Archivo de la etiqueta: Unreal Engine 4

UE4 – Consejo del día 2: Obtener el valor que tenía una variable antes de ser replicada

Sabías que …

Puedes obtener el valor que tenía una variable antes de ser replicada por el server. Solo necesitas agregar a la función OnRep_ un parámetro del mismo tipo de dato de la variable que se replica. Al llamarse esta función OnRep_ en los remotos, después de que el servidor modifica la variable, el valor actual de la variable será el valor modificado por el servidor, y el parámetro que recibe la función será el valor que tenía la variable antes de ser replicada.

Ejemplo: Vamos a crear la variable Health que se replique y la función OnRep_Health que se va a ejecutar automáticamente en los clientes remotos cuando el servidor modifica la variable Health.

//File: TipOfTheDayCharacter.h
//...
public:

UPROPERTY(BlueprintReadWrite, ReplicatedUsing = OnRep_Health, Category = "Stats")
float Health;

UFUNCTION()
void OnRep_Health(float LastHealthValue);

void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

//...

//File: TipOfTheDayCharacter.cpp

//...

void ATipOfTheDayCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(ATipOfTheDayCharacter, Health);
}

void ATipOfTheDayCharacter::OnRep_Health(float LastHealthValue)
{
	UE_LOG(LogTemp, Log, TEXT("Current Health Value: %f. Last Health Value: %f"), Health, LastHealthValue);
}

//...

Asumiendo que el valor inicial de la variable Health es 100 y el servidor modifica el valor en 90. Cuando en los remotos se dispara esta función, se imprimirá en la consola el siguiente log “Current Health Value: 90.0 Last Health Value: 100.0″ dado que en este punto, Health contiene el valor actual replicado por el servidor, y el parámetro LastHealthValue contiene el valor que tenía la variable antes de recibir la actualización del servidor.

😉

NOTA: Este post ha sido desarrollado usando el Unreal Engine 4.12.5, si estás trabajando con otra versión puede que encuentres algunas diferencias, ya que el Engine está en constante actualización. De ser así, déjame tus comentarios al final del post y buscamos juntos la solución.

English version: UE4 – Tip of the Day 2: Get the previous value of any variable after being replicated

Introducción al desarrollo de juegos multiplayer en Unreal Engine 4

English version: Introduction to the development of multiplayer games using Unreal Engine 4

Varias personas me han escrito con muchas dudas sobre el desarrollo de juegos multiplayer en Unreal Engine y me ha parecido muy buena idea comenzar una serie de tutoriales que ayuden ha entender bien a fondo como desarrollar juegos multiplayer con este magnifico motor. Como no hay mejor forma para aprender que practicando, pues eso es lo que vamos a hacer. Vamos a comenzar a desarrollar un simple “Third Person Multiplayer Shooter” en Unreal Engine. Esto nos va a permitir conocer muchas cosas nuevas que aún no hemos visto en tutoriales anteriores, y el plato fuerte, todo lo relacionado con el desarrollo multiplayer.

Como este asunto no es nada simple, ni tan siquiera en Unreal que tan fácil nos hace las cosas :), vamos a ir poco a poco, por lo que vamos a dejar este primer tutorial solo para una introducción en su mayoría teórica de todo este asunto del multiplayer.

NOTA: Este tutorial ha sido desarrollado con Unreal Engine 4.8, si estás trabajando con otra versión puede que encuentres algunas diferencias, ya que el Engine está en constante actualización. De ser así, déjame tus comentarios al final del post y buscamos juntos la solución.

Introducción

Un juego multiplayer o multijugador es un juego que es jugado por varios jugadores a la vez. En este punto tenemos dos variantes principales. La primera, donde los jugadores comparten una misma estación, cada uno con un control, y la pantalla se divide en partes. El hablar de este modo de juego multiplayer siempre me recuerda cuando jugaba de chico el GoldenEye 007 en un Nintendo 64 con todos los chicos del vecindario :)

Captura de pantalla del GoldenEye 007 donde se ve la pantalla dividida en dos partes, cada parte es el espacio de juego de cada jugador. En este caso hay dos jugadores jugando la partida con dos controles distintos. Imagen tomada de: http://www.meristation.com/nintendo-64/goldeneye-007/darwin-image/1507537/1155968

Captura de pantalla del GoldenEye 007 donde se ve la pantalla dividida en dos partes, cada parte es el espacio de juego de cada jugador. En este caso hay dos jugadores jugando la partida con dos controles distintos. Imagen tomada de:
http://www.meristation.com/nintendo-64/goldeneye-007/darwin-image/1507537/1155968

Otro ejemplo de juegos multiplayers donde los jugadores pueden jugar en la misma estación son los juegos de pelea, solo que en estos casos la pantalla no es dividida ya que ambos jugadores siempre están en la misma zona del nivel. Al igual que la modalidad anterior, cada jugador cuenta con un control para controlar a su personaje. Sin duda, como mejor ejemplo de este estilo de juego tenemos que citar al recién Mortal Kombat X, que dicho sea de paso, es uno de los grandes títulos desarrollados con Unreal Engine.

Captura de pantalla del Mortal Kombat X (Versión para móvil)

Captura de pantalla del Mortal Kombat X (Versión para móvil)

Las dos variantes mencionadas anteriormente se puede decir que son el principio de los juegos multijugador. Con el desarrollo de las redes de computadoras y el aumento en la velocidad de transmisión de datos por esta vía, surgió otro modo de juego multijugador, donde varios jugadores pueden jugar un mismo nivel, pero en este caso, en distintas estaciones, permitiendo a dos jugadores, incluso estando a kilómetros de distancia, compartir una partida.

… y bueno, ejemplos y las ventajas de esto no creo que sean necesarias mencionarlas, no !!?? :)

Bueno, en estos tutoriales nos centraremos en este último tipo de juego multijugador y vamos a comenzar comentando un poco la arquitectura de red más usada para soportarlos, hablamos de la arquitectura clientes/servidor.

Arquitectura Cliente/Servidor

En la arquitectura cliente/servidor cada uno de los jugadores son los clientes, y están conectados a una máquina centrar que será el servidor. El servidor es el responsable de todas las decisiones importantes del juego, manteniendo el estado del juego y transmitiendo esta información a cada uno de los clientes para que todos los jugadores estén sincronizados.

Diagrama de la arquitectura Cliente/Servidor. Imagen tomada del excelente video-tutorial “Blueprint Networking Tutorials (https://www.unrealengine.com/blog/blueprint-networking-tutorials)” de Billy Bramer (Lead GamePlay Programmer en Epic Game)

Diagrama de la arquitectura Cliente/Servidor. Imagen tomada del excelente video-tutorial “Blueprint Networking Tutorials (https://www.unrealengine.com/blog/blueprint-networking-tutorials)” de Billy Bramer (Lead GamePlay Programmer en Epic Game)

En esta arquitectura el servidor puede correr en dos modos distintos:

Listen server: En este modo el servidor también funciona como cliente. O sea, en el mismo ordenador que está haciendo función de servidor puede haber un jugador.

Dedicated Server: En este modo el servidor hace exclusivamente función de servidor, es en este modo en el que generalmente corre los servidores en producción.

Es importante destacar que en el modo Listen Server ese cliente no tiene ninguna exclusividad por estar jugando en el servidor, todas las reglas de la arquitectura se aplican de la misma forma ya que el cliente y el servidor son totalmente independientes aunque estén compartiendo el mismo hardware.

Representación gráfica del Listen Server y el Dedicated Server. Imagen tomada del excelente video-tutorial “Blueprint Networking Tutorials (https://www.unrealengine.com/blog/blueprint-networking-tutorials)” de Billy Bramer (Lead GamePlay Programmer en Epic Game)

Representación gráfica del Listen Server y el Dedicated Server. Imagen tomada del excelente video-tutorial “Blueprint Networking Tutorials (https://www.unrealengine.com/blog/blueprint-networking-tutorials)” de Billy Bramer (Lead GamePlay Programmer en Epic Game)

Unreal Engine 4 usa esta arquitectura Cliente/Servidor para soportar juegos multiplayers. Esto es un punto súper importante a tener siempre claro, el control total del juego lo llevará el servidor, y este será el encargado de actualizar el estado en todos los clientes . . . no te preocupes que entenderás mejor a que nos referimos con esto más adelante cuando comencemos con la práctica, pero de momento, ve grabándote esto “a fuego” en la cabeza, porque es la regla de oro.

Vamos a poner un ejemplo simple que ayude a aclarar el asunto.

Supongamos que estamos desarrollando un juego donde participan cuatro jugadores. Cada jugador tendrá su propia estación, o sea cada jugador será un cliente. Los jugadores puedes equipar armas que están regadas por el escenario para liquidar a los otros jugadores. El jugador “sobreviviente“ es el ganador.

Al comenzar a implementar este juego tenemos un primer problema. Cuando comienza el juego los cuatro jugadores salen en una posición determinada en el nivel, pero supongamos que el jugador 1 comienza a moverse. Lo que tendría que pasar es que en las otras 3 estaciones, el personaje del jugador 1 también se mueva, verdad?? Pues bien, para esto es que tenemos al servidor.

El jugador 1 en realidad lo que hará es “decirle“ al servidor que se está moviendo en x dirección. Esto lo hará básicamente enviando al servidor su vector de posición y rotación. El servidor se encargará de actualizar este valor en todos los clientes, incluyendo la validación del movimiento del jugador 1, que fue el que realizó la acción de caminar, y de esta forma en el momento en el que el Jugador 1 se mueve, en todas las estaciones, el personaje del jugador 1 también se mueve y así todos los clientes podrán ver lo mismo.

Esto pasa en cualquier acción en donde intervenga el estado de algún actor del juego, por ejemplo, si un jugador dispara, también se tiene que notificar al servidor de esto para que el servidor pueda actualizar en todos los clientes el momento de la acción, la posición del proyectil, etc. En fin, que la tarea de los clientes es notificar al servidor, y es el servidor el que se encarga de mantener actualizados a todos los clientes. Este concepto es muy importante que lo tengas siempre en cuenta y ya te digo, no te preocupes que lo veremos en la práctica.

Por supuesto, en todo este proceso intervienen varios temas bien conflictivos. Uno de ellos, por ejemplo, hablando del desplazamiento de los personajes, es evitar los saltos. Si en algún momento en el que el jugador 1 se está moviendo comienza a congestionarse la red, habrán algunos paquetes que no lleguen al servidor, y esto implicará que en los clientes, en vez de verse ese personaje desplazándose fluidamente de un punto a otro, se verá a saltos. Si has jugado juegos multiplayers en otras ocasiones, sobre todo en redes sobrecargadas y lentas, lo habrás notado de seguro.

En esos casos, hay que complicar un poco más la solución para evitar estos problemas. Generalmente se usan soluciones de interpolación de movimiento, para “predecir“ la posición del personaje con respecto al tiempo, y evitar estos saltos. Esto es fácil decirlo, pero la implementación no es nada trivial. . . por suerte estamos desarrollando con Unreal Engine :).

Lo mejor que tenemos al usar UE4 en el desarrollo de nuestros juegos, es que muchas veces no nos tenemos que preocupar por estos problemas “a bajo nivel“, verás que fácil es lograr esto con este Engine. La verdad esta ha sido una de las cosas que a mi en lo personal más me sorprendió cuando comencé a trabajar con Unreal, sobre todo porque tenía la experiencia de un proyecto multiplayer que desarrollé en Unity3D y SmartFoxServer . . . y en el que casi me vuelvo loco por tener que encargarme de todos estos detalles :(

Probando el modo multijugador desde el Editor

Bueno, después de haber hecho este pequeño recorrido por la teoría detrás de los juegos multiplayer con Unreal, vamos a pasar a desarrollar un simple ejemplo que nos permita poner en practica todos estos conceptos.

Para nuestro proyecto de ejemplo vamos a partir de la plantilla Tercera Persona desde C++. Crea un proyecto a partir de esta plantilla y ábrelo en el Editor.

El Unreal Editor nos facilita muchísimo a la hora de desarrollar un juego multiplayer, sobre todo para probar con distintos clientes sin la necesidad de tener distintas estaciones. Hasta ahora, para correr nuestro juego, dábamos clic en el botón de Play en el Toolbar y el juego era lanzado dentro del viewport en el editor, verdad ?. En el caso de un juego single player esto es suficiente, pero en el caso de un juego multiplayer, donde quieres probar distintas instancias (clientes) esta variante no nos es factible.

Si te fijas en el botón Play del Toolbar, este botón tiene al lado una flechita para desplegar más opciones. Al dar clic aquí se nos desplegará un menú con 3 secciones y la opción de Configuración Avanzada.

Captura del editor donde se ve el menú de opciones para jugar en el Editor.

Captura del editor donde se ve el menú de opciones para jugar en el Editor.

En la primera sección tenemos los distintos Modos de Play:

Selected ViewPort: Este es el modo que hemos usado hasta ahora para probar nuestro juego desde el Editor. Al seleccionar este modo el juego es lanzado dentro del viewport en el editor.

Mobile Preview: Nos lanza en una ventana nueva un simulador de móviles con dos Joystick virtuales sobre la pantalla. Muy útil para probar desde el ordenador los controles para móviles.

New Editor Windows: Al darle Play al juego en este modo, el juego es abierto en una ventana nueva independiente a la ventana del Editor. Esta es la forma más cómoda de probar nuestro juego en varios clientes, ya que cada cliente se abre en una ventana independiente que podemos acomodar en la pantalla para visualizar fácilmente el comportamiento del juego en esos clientes.

Standalone Game: El juego será lanzado en una nueva ventana pero que corre en su propio proceso. Por este motivo si corres el juego en este modo no podrás pausarlo o pararlo desde el Editor como hacemos normalmente.

Simulate: Esta opción es sumamente útil, yo la uso mucho testeando la IA de nuestro juego. Esta opción nos permite correr el juego en una especia de espectador, no tendremos el control de nuestro personaje, básicamente no tendremos un PlayerController que nos sirva de puente entre nosotros y nuestro Pawn, pero el resto del juego correrá normalmente y podremos “volar” por el escenario inspeccionándolo todo como queramos. Cuando estas corriendo el juego en este modo tienes total acceso a las herramientas del editor, o sea que puedes modificar la escena y su contenido a medida que el juego está corriendo. Juega un poco con este modo de Play, verás lo útil que te resultará.

PD: La opción VR Preview la verdad que no tengo ni idea ahora mismo de lo que es jejeje . . . esta opción la agregaron recientemente al Editor y tendrá que ver con todas las nuevas funcionalidades de Realidad Virtual de la versión 4.8 y posteriores. No he tenido tiempo de revisarlas así que si sabes de que va y te animas a dejármelo en el comentario me vas a sacar de una duda :)

A continuación de la sección Modos de Play tenemos la sección Spawn Player At. Aquí tenemos dos opciones: Current Camera Location y Default Player Start. Estas son bastante evidentes por su nombre. La primera nos permite que al lanzar el juego el Player salga en la posición y dirección en la que tenemos la cámara en el viewport y la segunda, es la que generalmente usamos hasta ahora, hace que el Player siempre salga en el Player Start que tenemos en el nivel.

Por último tenemos la sección Multiplayer Options, que es la sección que más nos interesa en este tutorial. En esta sección tenemos dos opciones:

Number of Players, es un campo para entrar la cantidad de jugadores con la que queremos probar nuestro juego.

Pon aquí 2, por ejemplo, y dale Play para que veas lo que pasa. Si en la sección Modes, mantienes la opción Selected Viewport verás que se lanzan dos instancias del juego una dentro del mismo editor y otra en una nueva ventana. Esto hace que las pruebas en los dos clientes sean un poco complejas, por lo que lo mejor para este caso es seleccionar New Editor Windows. De esta forma, al dar Play se abrirá cada una de las estaciones en ventanas independientes, y las puedes acomodar fácilmente dentro del espacio de la pantalla.

Por último, si estás trabajando en un monitor relativamente pequeño tendrás otro problema, el tamaño de cada una de las ventanas es muy grande. Esto lo podemos solucionar dando clic en la opción Advaced Settings del menú de Play. En la ventana que se abre (Editor Preferences/Level Editor/Play) verás la sección Play in New Window y la propiedad New Window Size. Ahí podrás modificar la resolución de la ventana para que puedas acomodar dos clientes (o los que quieras) en una sola pantalla. En mi caso, por ejemplo, generalmente redacto los tutoriales en un MacBook Air de 13 pulgadas, y para probar tengo que configurar la ventana para 640 x 480 :(.

Level Editor – Play donde se muestran las opciones para definir las dimensiones de la nueva ventana donde correrá el juego.

Level Editor – Play donde se muestran las opciones para definir las dimensiones de la nueva ventana donde correrá el juego.

Bien, después de configurar 2 Players, marcar como modo de Play, New Editor Windows, vuelve a lanzar el juego. Verás que se abren dos ventanas del mismo juego. Fíjate en el título de cada ventana:

Captura de pantalla donde se ven dos instancias o dos clientes del juego en ejecución.

Captura de pantalla donde se ven dos instancias o dos clientes del juego en ejecución.

Como puedes ver, por defecto el Unreal lanza una primera instancia que es el servidor en modo Listen. O sea, que también funciona como cliente. Es la ventana que en el título dice NombreDelProyecto Preview Server, el otro es un cliente normal. Probar el juego con el servidor en modo Listen siempre es de mucha ayuda porque muchas veces cuando estamos desarrollando nos pasa que notificamos en el servidor, en el servidor se realiza la acción correctamente pero no se replica al resto de los clientes. Al correr el juego en este modo podemos ver que la instancia del cliente que está corriendo en el server si verá la acción que sucedió correctamente, pero los otros no. Dado que nos está faltando implementar que el server notifique a todos los clientes, con esto podemos saber por donde comenzar a buscar :)

Además de esto Unreal nos permite también testear nuestro juego mutiplayer en un servidor dedicado. Fíjate al desplegar las opciones de Play desde el Toolbar que debajo de la opción para definir el número de clientes tienes un check que dice Run Dedicated Server. Si lo seleccionas y das Play verás que al igual que el caso anterior, se ejecutarán dos instancias de nuestro juego, pero si te fijas en el nombre de cada ventana ambas dicen clientes, porque el servidor está corriendo también pero en modo dedicado (sin salida gráfica).

Muy bien, como verás en cada una de las instancias puedes controlar al personaje y si te mueves con una en el otro cliente se ve perfectamente ese personaje moviéndose. Al menos yo, cuando vi esto, mi expresión fue de: 😮 !! ¿Pero cómo, si no he hecho absolutamente nada ¡!!? . . . no he escrito ni una línea de código para lograr todo esto, y recuerdo que en mi proyecto Unity hace un tiempo, para lograr solamente esto, que al moverme con un personaje se actualicen correctamente todos los clientes, me tomó una semana y tuve que programar “como loco“, incluidos métodos para interpolación.

Pues sí, así mismo, y vamos a aprovechar este asombro que de seguro tienes como mismo tuve yo, para comentar un atributo prácticamente mágico que tiene Unreal en los Actores y es el atributo Replicate

Replicated Actors en Unreal Engine 4

Si vas a la sección Default de cualquier Actor, ya sea el PlayerCharacter o cualquier otro actor del juego, verás que cuenta con una sección de nombre Replication. Esta sección contiene la mayoría de las propiedades que definen el comportamiento de este actor en un juego multiplayer. Ahí verás la propiedad de tipo bool, Replicates. Si abres, por ejemplo, el blueprint del Character, verás que esta propiedad está en true (marcado el checkbox) así como la propiedad Replicate movement.

networking_tuto_01_image_08

Pues tan simple como esto, solamente con marcar en un actor estas dos propiedades en true el Engine se encarga de manejar todo lo necesario para cuando este actor se mueva por el escenario el servidor actualice en todos los clientes la posición y rotación de ese Actor 😮 !!!

Vamos a hacer una pruebilla muy simple, desmarca la propiedad Replicate Movement y vuelve a correr el juego con dos clientes. Verás que en este caso siguen saliendo los dos Players pero cuando te mueves con uno, su personaje en la otra instancia no se mueve. Por tanto, en todo Actor donde queramos que se replique la posición del mismo a travez de la red (aplicando mecanismos de interpolación etc) tenemos que dejarle en true esta propiedad.

La propiedad Replicates básicamente debe estar en true para todos los actores que tengan relevancia a través de la red. Lo veremos más adelante con las atributos que queramos que se repliquen.

Muy bien, pues sin escribir una línea de código (ni crear un nodo en el Blueprint) tenemos todo el mecanismo de replicación del Player Character entre todos los clientes del juego. Perfecto !! es un genial punto de partida para nuestro juego multijugador.

Conclusión

Vamos a dejar esta primera parte introductoria aquí para comenzar en el siguiente tutorial con la práctica. Como les comentaba al inicio, la idea es desarrollar un juego simple tercera persona y multiplayer, donde podrán jugar varios jugadores y el ganador será el que sobreviva. Además de los conceptos y las técnicas relacionadas con el multiplayer que será el plato fuerte de esta serie, también el ejemplo nos permitirá ver como implementar muchísimas cosas nuevas que aún no hemos visto en los tutoriales anteriores, así que no te vayas muy lejos que pronto estamos por aquí con la segunda parte . . . hasta entonces, bye 😉

English version: Introduction to the development of multiplayer games using Unreal Engine 4

Introducción al HUD en Unreal Engine 4 – Parte 1

En los videojuegos, el HUD (Head Up Display) es el contenedor con toda la información que se encuentra sobre la vista de la cámara (como pegatinas en el lente de la cámara). Su propósito es informar al jugador del estado actual del juego, como puede ser, la salud de su personaje, la energía, las municiones o el mini mapa. El HUD no solo sirve para mostrar en un primer nivel la información propia del personaje protagónico, también nos sirve para mostrar cualquier información que pueda ser útil, por ejemplo, un caso que se usa mucho en los juegos de estrategia, es mostrar el indicador de salud sobre los personajes.

En este tutorial veremos cómo implementar el HUD de nuestro juego en Unreal Engine 4, para que el usuario tenga siempre a la vista la salud de su personaje, el arma que tiene equipada y las municiones que le quedan. También implementaremos un indicador de salud sobre los enemigos al estilo de los juegos de estrategia.

NOTA: Este tutorial ha sido desarrollado con Unreal Engine 4.6.1, si estás trabajando con otra versión puede que encuentres algunas diferencias ya que el Engine está en constante actualización. De ser así, déjame tus comentarios al final del post y buscamos juntos la solución.

El HUD en Unreal Engine 4

En Unreal Engine la clase GameMode es el núcleo de nuestro juego. Desde el Editor podemos definir fácilmente el GameMode que usará el juego dando clic en la opción World Settings del Toolbar y buscando la sección Game Mode, como hemos hecho ya en tutoriales anteriores.

La clase GameMode tiene cuatro atributos puntales de nuestro juego, hasta ahora hemos visto dos de ellos.

Default Pawn Class: Representa el Pawn del personaje protagónico, el que controlará el jugador

Player Controller Class: Es el Player Controller que poseerá al Pawn para poderlo controlar, es como la interfaz entre el jugador y el personaje dentro del juego.

Además de estos, el GameMode tiene dos atributos más, muy importantes:

Game State Class: Contiene el estado del juego, por ejemplo, la lista de misiones que has ganado, la puntuación actual del jugador y todas las cosas que en general determinen el estado del juego. Hasta ahora siempre hemos usado la clase por defecto que nos brinda el Engine.
Por último, el Game Mode tiene el atributo que veremos en este tutorial, el HUD Class.

HUD Class: Este atributo es una instancia de la clase AHUD que nos brinda el framework y nos permite definir la clase que implementará el HUD de nuestro juego. La clase AHUD es la que nos permitirá dibujar todo el contenido que se mostrará sobre la pantalla. Cada PlayerController tiene una instancia de su propio HUD y lo que se dibuje mediante ese HUD será dibujado en el ViewPort de ese Player Controller. En el caso de los juegos multiplayers que dividen la pantalla, varios ViewPorts comparten la misma pantalla, pero cada HUD dibuja su contenido en su propio Viewport, o sea, en su propio espacio en la pantalla.

La clase AHUD cuenta con un Canvas (instancia de UCanvas) sobre el que podemos pintar texturas, textos, rectángulos etc. Sobrescribiendo el método DrawHUD podemos dibujar sobre el HUD toda la información que queramos con métodos como DrawTexts o DrawRect y muchos otros que puedes ver en la declaración de la clase en los fuentes del Engine.

En versiones anteriores del Unreal este era el mecanismo por excelencia para implementar el HUD de nuestro juego. Crear una clase que herede de AHUD, sobrescribir el método DrawHUD para dibujar todo lo que necesitáramos e inicializar el HUDClass del GameMode con una instancia de esta clase. Pero en las versiones más reciente del Engine también contamos con una herramienta mucho más a “alto nivel“ y mucho más cómoda, que nos permite implementar nuestro HUD básicamente mediante “Drag and Drop“ de componentes, el Unreal Motion Graphics (UMG).

Unreal Motion Graphics (UMG) es una herramienta que nos brinda el Engine para crear los elementos UI como el HUD, menues del juego etc. El núcleo del UMG son los Widgets, que no son más que una serie de componentes preconstruidos que podemos usar para confeccionar la interfaz que queramos. Entre estos Widgets tenemos botones, checkboxes, barras de progreso y montón de componentes muy cómunes en las interfaz y que podemos incluir y customizar muy facil.

Estos Widgets son editados desde el Widget Blueprint. Un editor blueprint muy parecido al Blueprint Editor con el que hemos trabajado pero que tiene dos modos, el Graph y el Designer. El Graph ya sabemos para lo que es :) y el Designer es un espacio de trabajo donde podemos diseñar, al estilo “Drag and Drop“, la interfaz.

En este tutorial vamos a explorar ambos mundos. Vamos a implementar la parte del HUD que muestra toda la información del Player mediante el UMG y vamos a crear nuestra propia clase AHUD para mostrar también en el HUD, sobre los enemigos, una barra de salud a modo de indicador. De esta forma podremos dar un acercamiento a las dos variantes y quedará por tu parte seleccionar la vía que más te agrade cuando vayas a desarrollar el HUD de tu juego.

Nota: Para el desarrollo de este tutorial vamos a partir del proyecto que terminamos en el tutorial: Implementando un AI Character con un arma en UE4 donde ya tenemos todas las cosas necesarias del gameplay. Esto nos permitirá centrarnos exclusivamente en lo referente al HUD.

En este tutorial vamos a implementar el HUD para que el jugador tenga siempre a la vista la información del estado del juego (salud nuestra, salud del enemigo, arma equipada, cantidad de municiones, etc). Al terminar el HUD de nuestro juego se verá así:

Captura del juego donde se ve como quedará el HUD una vez que terminemos las dos partes que conforman este tutorial

Captura del juego donde se ve como quedará el HUD una vez que terminemos las dos partes que conforman este tutorial

 

Bien, despues de toda esta teoría ya podemos pasar a la práctica. Lo primero que vamos a agregar al HUD de nuestro juego será una barra de progreso que refleje la salud del personaje, y para esto usaremos los Widgets y el Unreal Motion Graphic.

Creando una barra de progreso que refleje la salud del personaje mediante el Unreal Motion Graphics (UMG)

Crea una nueva carpeta en el Content Browser, una vez dentro da clic derecho en un espacio en blanco y selecciona User Interface/Widget Blueprint, nómbralo HUDWidgetBlueprint y ábrelo.

Opción de widget blueprint para la creación del HUD

Opción de widget blueprint para la creación del HUD

 

Esto nos abrirá el editor de los Widget Blueprint. En el modo Designer es donde diseñaremos nuestro HUD. Has un poco de zoom-out al viewport viewport (usando la rueda del mouse) hasta que veas un cuadro azul. Este cuadro representa el espacio de la pantalla y será el contenedor del HUD de nuestro juego. Todos los componetes que agreguemos aquí se veran en un primer plano (como pegatinas en el lente de la cámara).

Captura del Widget Blueprint en el modo Designer

Captura del Widget Blueprint en el modo Designer

 

En el panel Palette (a la izquierda del editor) podrás encontrar agrupados por categoría todos los widgets que podemos incluir en nuestro HUD, tómate unos minutos para que le des un vistazo a todos los componentes que ya tenemos listos para usar. Busca el Progress Bar que se encuentra en la sección Common y arrástralo al contenedor del HUD en el centro de la pantalla y colócalo centrado en el borde inferior.

Paleta de componentes

Paleta de componentes

 

Una vez agregado a la escena, selecciónalo. A la derecha del editor sale el panel de detalles con las propiedades de este componente. También tómate unos minutos para dar un vistazo por todas las propiedades, para que tengas una noción de todo lo que se puede personalizar.

Panel de detalles

Panel de detalles

 

En la sección Appareance tenemos la propiedad Percent. Esta propiedad define el porciento de progreso de la barra. Inicialmente vamos a ponerle valor 1.0 (100%) aunque en realidad el valor de progreso de esta barra estará vinculado al valor de salud del personaje, más adelante veremos cómo hacerlo.

Captura del WidgetBlueprint con la barra de progreso agregada dentro del espacio de trabajo

Captura del WidgetBlueprint con la barra de progreso agregada dentro del espacio de trabajo

 

En este punto, es válido comentar un detalle. Al correr el juego en distintas dimensiones de pantalla no siempre nos saldrá el componente en la misma posición en la que lo colocamos aquí desde el Designer. Para solucionar este problema el UMG nos brinda varias herramientas para anclar los elementos a determinados lugares de la pantalla y que la posición que le definamos sea relativa a ese anclaje. Selecciona el Progress y en el panel Details busca la propiedad Anchors, despliega el combobox y verás un grupo de opciones que gráficamente nos muestran cual será el comportamiento del anclaje del elemento. En nuestro caso selecciona la que indica que el componente quedará anclado en el borde inferior al centro de la pantalla. Puedes jugar un rato con las otras opciones y moviendo la barra de progreso de lugar para que veas su comportamiento.

Modificación de Anchors del Progress

Modificación de Anchors del Progress

 

Por último en el panel Details, encima del buscador, a la izquierda del checkBox IsVariable se encuentra el nombre que tendrá este elemento. Por defecto Unreal le asigna uno, cámbialo para tenga un nombre más descriptivo y así poder identificarlo más rápido a la hora de buscarlo entre varios elementos. En mi caso le puse HealthProgress.

Bien, de momento solo tendremos eso en el HUD de nuestro juego, una barra de progreso en el borde inferior. Compila, salva los cambios y cierra este editor.

Agregando el HUDWidgetBlueprint al ViewPort

Ya tenemos diseñada una primera versión del HUD que queremos en nuestro juego, pero aún nos falta agregarlo al Viewport y esto lo haremos desde el Level Blueprint.

Mediante el botón Blueprint del Toolbar selecciona Open Level Blueprint. Lo que haremos aquí será muy simple. En el Event Begin Play vamos a crear una instancia del HUDWidgetBlueprint mediante el nodo CreateWidget. Una vez creada una instancia de este widget, la asignaremos a una variable que crearemos previamente, para en el futuro poder acceder fácilmente al HUD. Por último agregaremos este HUDWidgetBLueprint al ViewPort mediante el nodo Add to Viewport.

Captura del Level Blueprint donde se crea una instancia del WidgetBlueprint y se agrega al Viewport.

Captura del Level Blueprint donde se crea una instancia del WidgetBlueprint y se agrega al Viewport.

 

Compila, salva, cierra este editor y corre el juego. Verás el HUD en la pantalla. Pero tenemos pendiente un detalle, cuando nos acercamos al enemigo y este nos dispara, se disminuye la salud del PlayerCharacter porque vemos que llega a morir si recibe muchos disparos, pero la barra de progreso ni se entera. Vamos a ello.

Escenario con la salud del personaje

Escenario con la salud del personaje

 

Ya tenemos la barra de progreso que nos permitirá saber en todo momento la salud que tiene nuestro personaje, pero si te dejas dar un tiro por el enemigo verás que la barra no refleja nada, continua estando en el 100%. Pues es precisamente esto lo que vamos a solucionar ahora, vamos a “bindiar“ el progreso de la barra de estado a la salud del personaje para que cada vez que la salud de nuestro héroe disminuya la barra haga lo mismo.

Creando “Binding” para definir el progreso de la barra de forma dinámica

Abre el HUDWidgetBlueprint, selecciona la barra de progreso y en el panel Details en la sección Appearance en la propiedad Percent da click al boton Bind/Create Binding como se muestra en la imagen.

Creando un Binding para definir el dinámicamente el progreso de la barra de salud

Creando un Binding para definir el dinámicamente el progreso de la barra de salud

 

Una vez que des en el botón Create Binding se abrirá un blueprint editor donde podremos implementar el algoritmo necesario para definir en todo momento el porciento de progreso de esta barra. Como esta barra de progreso representa la salud del personaje será muy simple la implementación de este método. Obtenemos la referencia de nuestro héroe, obtenemos el valor de su variable Health, llevamos ese valor a porciento (dividiendo entre 100) ya que es un valor entre 0 y 1 el que espera este componente y finalmente conectamos este número al Return Value de esta función.

Función que determinará dinámicamente el porciento de progreso que tendrá el ProgressBar del HUD

Función que determinará dinámicamente el porciento de progreso que tendrá el ProgressBar del HUD

 

Compila, guarda, cierra ese editor y dale Play al juego. Acercate al enemigo para que te dispare y verás como cada vez que disminuye la salud de nuestro héroe esta queda perfectamente reflejada en la barra de progreso del HUD.

Escenario con nuestro enemigo disparando y se observa como se refleja en la salud del personaje

Escenario con nuestro enemigo disparando y se observa como se refleja en la salud del personaje

 

Personalizando el estilo de la barra de progreso

Ya tenemos un HUD con una barra de progreso que representa la salud de nuestro personaje, pero el estilo de esta barra es bastante insípido, verdad ? Vamos a jugar un poco con las propiedades que nos brinda este componente para mejorar su look. Lo que haremos será agregar una textura de fondo para el componente, otra a la barra de progreso y por último un icono a la derecha de la barra como indicador de lo que representa esa barra de progreso. Al final nuestra barra de progreso quedará así:

Estilo del Progress Bar para la salud del personaje

Estilo del Progress Bar para la salud del personaje

 

Primero descárgate los recursos para este tutorial desde aquí. Al descomprimir este .zip encontrarás los siguientes archivos:

HealthIcon: Icono para identificar la salud del personaje.
ProgressBackground: Fondo del progress bar para la salud del personaje.
ProgressFillImage: Imagen utilizada mientras se va llenando el progress bar para la salud del personaje.

De vuelta al Unreal Editor, en el Content Browser, dentro de la carpeta HUD, crea una nueva carpeta con el nombre Textures e importa aquí las texturas descargadas.

Al importar las texturas te saldrá un cartel como el que se muestra en la imagen, dale Yes.

Alerta al importar texturas ¨non power of two¨

Alerta al importar texturas ¨non power of two¨

 

El motivo de este alert viene dado por lo siguiente. Cada textura que importes al proyecto, por lo general su tamaño debe ser una potencia de 2, es decir 2n por ejemplo “8”, “16”, “32”, “64”, “128”, “256”, “512”, “1024”, “2048” … Texturas que se importen y que no cumplen esta regla, pierden calidad mientras más lejos estás de ellas. Además, el Engine no las puede optimizar para que puedan ser cargadas rápidamente y procesarla en la memoria. Puedes profundizar un poco sobre este tema en el siguiente link . En nuestro caso esto no nos afecta, por eso puedes aceptar el mensaje sin preocupación.

Una vez importadas las texturas, abre el blueprint del HUD y selecciona el Progress. En el panel Details, desplázate hasta la sección Style. Las propiedades dentro de esta sección nos sirven para modificar el estilo de los componentes. En nuestro caso vamos primero a modificar la propiedad Fill Image que nos permitirá definir el estilo del relleno de la barra. Expande Fill Image y en la propiedad Image despliega el combo a su lado y asígnale la imagen ProgressFillImage que acabamos de importar. Después, expande la propiedad Tint y en el parámetro A asígnale valor 0.7, para darle un poco de transparencia a la imagen.

Cambiando el estilo de la salud del personaje

Cambiando el estilo de la salud del personaje

 

Ahora vamos a personalizar el fondo de la barra. Repite el proceso pero en este caso en la propiedad Background y usa como imagen de fondo la textura ProgressBackground.

Ya casi terminamos, solo nos queda colocar un pequeño icono a la derecha de la barra para que el jugador pueda saber a la primera que esta es la barra de salud de su personaje. Para esto vamos a usar un componente de tipo Image.

Agrega un componente de tipo Image desde la paleta de componentes y asígnale como imagen, en la propiedad Brush/Image, el HealthIcon que importamos. Ahora aquí vamos a topar con un detalle, y es que este elemento se va a superponer sobre un pedazo de la barra de progreso. Como primero agregamos la barra de progreso y después agregamos este componente no tendremos problema, y la imagen nos saldrá por arriba, pero puede darse el caso que fuera al revés y el iconito quedará tapado por la barra de progreso. Para solucionar este problema en cada componente tenemos la propiedad ZOrder que define el orden en el eje z de cada elemento agregado al HUD.

Listo!! ya tenemos terminada la barra que reflejará la salud del personaje en el HUD de nuestro juego. Compila salva lo cambios y dale Play. Muévete hasta el enemigo y deja que te dispare para que veas como disminuye el progreso de la barra a medida que disminuyen las vidas del personaje, y ahora con el estilo que queríamos.

Captura de pantalla del juego donde se ve disminuyendo la barra de progreso que representa la salud del personaje.

Captura de pantalla del juego donde se ve disminuyendo la barra de progreso que representa la salud del personaje.

 

Perfecto !! si has llegado hasta aquí ya tendrás una noción básica del Unreal Motion Graphic y de cómo implementar el HUD de nuestro juego mediante esta herramienta, de momento vamos a dejarlo aquí. En el próximo tutorial vamos a continuar este proyecto y agregaremos nuevos componentes para mostrarle al jugador el arma que tiene equipada, el arma secundaria y la cantidad de municiones restantes.

Ahora . . . como hablamos al inicio, existe otra forma un poco más ¨a bajo nivel¨ pero que creo que vale la pena darle un vistazo. Para esto vamos a implementar un mecanismo para mostrar en el HUD de nuestro juego de forma dinámica, sobre la cabeza de cada enemigo, una barra de salud. Pero en este caso lo haremos heredando de la clase AHUD y sobrescribiendo el método DrawHUD.

Indicador de vidas sobre del enemigo (variante C++)

Lo primero que haremos será crear una nueva clase C++ que herede de HUD y con una instancia de esta clase inicializaremos el atributo HUDClass del Game Mode.

Crea una nueva clase (Add code to project) que herede de AHUD y nómbrala UE4DemoHUD (o como prefieras). Ahora abre el .cpp de tu clase GameMode e inicializa el atributo HUDClass con una instancia de esta nueva clase que acabamos de crear. Para esto, agrega lo siguiente antes de terminar la implementación del constructor del GameMode:

HUDClass = AUE4DemoHUD::StaticClass();

Recuerda incluir el .h de la clase que acabamos de crear para que desde aquí el compilador la pueda localizar.

Ahora vamos a sobrescribir el método DrawHUD de la clase HUD para dibujar lo que queremos. El método DrawHUD es llamado por el Engine automáticamente en cada frame del juego y es aquí donde tenemos que dibujar todo lo que queramos mostrar en el HUD en cada frame. En nuestro caso lo que queremos es dibujar una barra de progreso muy simple pero que siempre se vea sobre cada personaje enemigo y para esto tenemos que hacer algunas cosillas. Aquí te dejo como tiene que quedar el método, tómate unos minutos y ves línea a línea con los comentarios para que entiendas lo que hace.


//En el UE4DemoHUD.h
virtual void DrawHUD() override;

//En el UE4DemoHUD.cpp
#include “AIEnemyCharacter.h“

void AUE4DemoHUD::DrawHUD()
{
    ///Arreglo de actores para pasar por referencia al GetAllActorsOfClass
    //y obtener todos los actores del tipo de clase indicada
    TArray<AActor*> OutActors;
    
    //Guarda en el arreglo que pasamos como tercer parámetro todos los actores que estén en el nivel del tipo de clase indicado en el segundo parámetro
    UGameplayStatics::GetAllActorsOfClass(this, AAIEnemyCharacter::StaticClass(), OutActors);
    
    //Recorre cada uno de los actores devueltos por el GetAllActorsOfClass
    for (int32 Idx = 0; Idx < OutActors.Num(); ++Idx)
    {
        //Obtiene el actor actualmente en el loop casteado ya a la clase de nuestro enemigo
        AAIEnemyCharacter *AiEnemy = Cast<AAIEnemyCharacter>(OutActors[Idx]);
        
        // Si el cast fue satisfactorio y aún está vivo (su salud es mayor que 0)
        if (AiEnemy != NULL && AiEnemy->GetHealth()>0)
        {
            //Offset a la posición del personaje para que le quede el indicador de salud por encima de la cabeza
            FVector Offset = FVector(0, 0, 150);
            
            //Obtenemos la posición del Actor en el mundo
            FVector Location = AiEnemy->GetActorLocation();
            
            //Calculamos la posición donde queremos mostrar el indicador (encima de la cabeza del enemigo)
            //sumándole al vector de la posición del enemigo el offset
            FVector RectLocation = Location + Offset;
            
            //Este es uno de los puntos claves en este método. La clase AHUD cuenta con el método Project que nos permite convertir
            //coordenadas 3D al plano 2D del canvas. Necesitamos convertir las coordenadas RectLocation para poder dibujar el indicador
            //en el Canvas del HUD pero siempre en la posición correcta (sobre el enemigo)
            FVector RectLocation2D = Project(RectLocation);
            
            //Dibuja un primer rectángulo de color rojo que simulará el fondo de una barra de progreso
            //DrawRect nos permite dibujar un rectángulo en el canvas del HUD y espera los siguientes parámetro:
            //Color del rectángulo, posición en X en el canvas, posición en Y en el canvas ancho y alto
            DrawRect(FLinearColor(1.0, 0.0, 0.0, 1.0), RectLocation2D.X - 50, RectLocation2D.Y, 100, 15);
            
            //Dibuja un segundo rectángulo de color verde que será la propia barra de progreso
            //En este caso en vez de dibujar este rectángulo con un ancho de 100, lo dibujaremos con un ancho equivalente
            //a la salud del personaje que puede estar en un rango entre 0 - 100.
            //Como este rectángulo quedará sobre el anterior, la composición simulara una barra de progreso simple.
            DrawRect(FLinearColor(0.0, 1.0, 0.0, 1.0), RectLocation2D.X - 50, RectLocation2D.Y, AiEnemy->GetHealth(), 15);
        }
    }
} 

Listo !!. Compila el proyecto y en el editor desde el Toolbar ve hasta el World Settings y en la sección Game Mode asegúrate que se está usando el GameMode de nuestro juego y que al desplegarlo, en el atributo HUD Class esté indicado el nombre de la clase HUD que acabamos de crear.

Captura de nuestro World Settings donde se observa nuestro HUD

Captura de nuestro World Settings donde se observa nuestro HUD

 

En este punto me gustaría comentar un detalle. En versiones anteriores del Unreal Engine, al agregar una clase C++ al proyecto se tenía que cerrar el editor y volver a compilar y correr desde el IDE para que el editor pudiera encontrar esa clase. A partir de la 4.6.1 ya esto no es necesario. Una de las mejoras que incluyeron a partir de esta versión es que no es necesario cerrar el editor, simplemente al compilar el proyecto el Editor automáticamente detecta y recarga las nuevas clases o cambios hechos en las ya existentes. Para eso solo hace falta compilar el proyecto con el Editor abierto, fíjate que no tienes que Compilar y Correr, solo Compilar. Inmediatamente después que termine la compilación, verás como el Editor recarga los cambios.

Después de compilar, dale Play al juego y acércate al enemigo. Verás como sobre él se muestra una barrita como indicador de salud y si le disparamos la barra se actualiza. En el momento de morir deja de aparecer la barra por la condición que pusimos en el DrawHUD de solo dibujar si las vidas de ese Actor son mayores que 0.

Enemigo con su barra de salud

Enemigo con su barra de salud

 

Indicador de vidas sobre del enemigo (variante Blueprint)

A modo de referencia vamos a ver como sería para lograr esto mismo totalmente desde Blueprint.
Primero crea un nuevo blueprint dentro de la carpeta HUD que herede de HUD. En mi caso le puse HUDBlueprint.
Abre el archivo .cpp del GameMode y tenemos que cambiar para en vez de asignar al atributo HUDClass una instancia de la clase que creamos desde C++, asignarle este blueprint. Para esto sustituye la línea HUDClass = AUE4DemoHUD::StaticClass(); por lo siguiente:

//Obtiene la referencia al Blueprint acabado de crear
static ConstructorHelpers::FObjectFinder<UClass> HUDBPClass(TEXT("Class'/Game/HUD/HUDBlueprint.HUDBlueprint_C'"));

//Inicializa el atributo HUDClass con el HUDBlueprint creado y configurado desde el editor
if (HUDBPClass.Object != NULL)
{
  HUDClass = HUDBPClass.Object;
}

Compila nuevamente el proyecto y asegúrate desde el WorldSettings/GameMode que ahora el HUD Class del GameMode sea este blueprint que acabamos de crear. Una vez hecho esto abre el HUDBlueprint en el modo Graph y vamos a implementar aquí el mismo algoritmo que implementamos en el DrawHUD desde C++.

Agrega el nodo Event Received Draw HUD que es el nodo equivalente al DrawHUD que vimos desde C++ y en este evento implementa el mismo algoritmo que ya hicimos.

Event Received Draw HUD del HUDBlueprint para imprimir en el HUD el indicador de vidas sobre cada enemigo.

Event Received Draw HUD del HUDBlueprint para imprimir en el HUD el indicador de vidas sobre cada enemigo.

 

Compila, salva los cambios y corre el juego. El resultado será el mismo que ya logramos anteriormente desde C++ pero ahora hecho totalmente desde blueprint.

Conclusión

Como has podido notar el Unreal Motion Graphics nos facilita muchísimo la vida a la hora de crear las interfaz al tenernos todos estos Widgets listos para usar. Pero de cualquier forma el DrawHUD y los métodos para dibujar sobre el Canvas también son una alternativa (y muchas veces son el único camino posible).

Te recomiendo que le des un vistazo a la declaración de la clase HUD del Engine para que veas todos los métodos que puedes usar para dibujar distintos elementos en el Canvas. También puedes visitar los siguientes enlaces:

UCanvas
HUD, Canvas, Code Sample of 800+ Lines, Create Buttons & Draw Materials

Otro detalle importante a tener en cuenta es que hemos implementado funcionalidades para el HUD de nuestro juego de dos formas distintas, y como ves, pueden coexistir sin problema, así que no tienes que limitarte por una vía u otra, simplemente úsalas en conjunto para lograr un espectacular HUD en tu juego 😉

Bueno, esto es todo por hoy. Vamos a dejar aquí la primera parte de este tutorial de Introducción al HUD en Unreal Engine 4. En el próximo tutorial vamos a continuar este proyecto y agregaremos al HUD nuevos componentes, mediante el Unreal Motion Graphic, para mostrarle al jugador el arma que tiene equipada, el arma secundaria y la cantidad de municiones restantes. También veremos algunos truquitos para animar los elementos en el HUD.
Mientras tanto, bueno ya sabes . . . nos encantaría escuchar tus comentarios.

Introducción a los Delegates en Unreal Engine 4

En el desarrollo de videojuegos algo que necesitamos muy a menudo es notificar a los distintos elementos del juego cuando ocurre algún evento, por ejemplo, cuando un personaje muere, cuando las fuerzas enemigas comienzan a atacar nuestra base, cuando alcanzamos un objetivo determinado del juego . . . en fin, en muchísimos casos. Es precisamente en estas situaciones donde nos vienen a ser de gran ayuda los Delegates.

NOTA: Este tutorial ha sido desarrollado con Unreal Engine 4.6.1, si estás trabajando con otra versión puede que encuentres algunas diferencias, ya que el Engine está en constante actualización. De ser así, déjame tus comentarios al final del post y buscamos juntos la solución.

Imaginemos un caso real. Supongamos que tenemos en nuestro juego al personaje protagónico y a un grupo de distintos personajes secundarios, ya sean enemigos, aliados o lo que sea, y queremos ejecutar algún comportamiento en cada uno de estos otros personajes cuando el PlayerCharacter muere.

Una vía de hacer esto es obtener la referencia del Player Character en el “Event Tick“ de cada uno de esos personajes, y preguntar por alguna variable que tengamos creada en el Character que defina su estado en el juego.

Esta solución funciona, es verdad . . . pero es terriblemente mala !!, porque cada uno de los actores tienen que estar ejecutando un método constantemente para poder saber el estado del Player, con el consiguiente gasto de recurso que esto implica, sobre todo si estamos hablando de un juego relativamente grande, donde varios actores estén ejecutando esto en paralelo. Esto sería mucho más desastroso si el proceso no fuera solo preguntar por el valor de alguna variable, sino realizar algún calculo o algo por el estilo, aquí el gasto de recursos sería aún mayor.

Pues es precisamente esta una de las situaciones en donde los Delegates vienen a salvarnos la vida. Un Delegate nos dan la posibilidad de “bindiar“ a él uno o más métodos de otras clases. De esta forma podemos en cualquier momento “llamar“ este Delegate y esto nos permitirá ejecutar en cada una de esas instancias que “bindiaron” métodos a él, esos métodos. O sea, que esto nos permite ejecutar un método de otra clase en el momento que queramos, sin ni tan siquiera tener acceso directo a ese método. Genial no !!??

Pues si volvemos a nuestro ejemplo, para solucionar este mismo problema podemos crear un Delegate en la clase del personaje protagónico. Al iniciar el juego (o en cualquier otro punto que queramos), le decimos a los otros Actores que registren un método de ellos que se va a llamar inmediatamente en el momento en el que este Delegate sea lanzado. Hecho esto, en el momento en el que el Player muere, ”lanzamos“ ese Delegate, y automáticamente será llamado en todas las instancias de las clases que “bindiaron” métodos a él, ese método. Método que tendría una implementación particular en cada una de las clases. Por ejemplo, en el enemigo se puede ejecutar alguna animación de alegría, y en el aliado, el método puede ejecutar alguna animación de tristeza, o puede atacar directamente al enemigo . . . o cualquier otra cosa mucho más original y menos absurda :)

Mucho mejor esta solución, verdad ?

Para entenderlo mejor y ver en la práctica como usarlo, vamos a preparar un pequeñito ejemplo. Veremos primero como trabajar con Delegates desde C++ para entenderlo todo a bajo nivel, después veremos la implementación desde los Blueprints, y queda por tu parte seleccionar la vía que más te guste cuando los vayas a implementar en tu juego 😉

Para este simple ejemplo he creado un proyecto nuevo en UE4 a partir de la plantilla Top-Down en C++ y le he agregado 2 nuevas clases de Characters: RedCharacter y GreenCharacter. Después de esto he creado dos Blueprints a partir de estas clases, RedCharacterBlueprint y GreenCharacterBlueprint. Para el Skeletal Mesh de ambos he usado el mismo que usa MyCharacter al igual que con el Blueprint Animation.

RedCharacter y GreeCharacter podemos verlos como dos personajes cualesquiera que puede tener nuestro juego.

Para diferenciarlos un poco vamos a crear dos Materiales muy simples, que sean de un color completo. Los Materiales en Unreal son un tema bastante complejo y están más en el campo de los diseñadores, así que no nos detendremos en ellos ahora mismo. Para crear un material da clic derecho en el Content Browser dentro de la carpeta donde lo quieras crear, selecciona Material y dale un nombre, en este caso yo lo voy a llamar RedMaterial. Dale doble clic para abrir el Material Editor que es el Editor de Materiales que viene con el Unreal Editor.

Crea un nodo de tipo “Constant 3Vector“ y en el panel de detalles de este nodo da clic en la propiedad Constant y selecciona el color rojo en el Picker Color que se te mostrará. Por último conecta este nodo al Base Color del nodo RedMaterial. Con esto hemos creado un material súper simple que al aplicarlo en el Mesh del RedCharacter hará que este se vea rojo en su totalidad, así lo podremos distinguir fácilmente.

Captura del RedMaterial en el Material Editor

Captura del RedMaterial en el Material Editor

 

Por último, abre el RedCharacterBlueprint en el modo Components, selecciona el Mesh y en la sección Rendering verás la propiedad Materials que es un array. Agrega un elemento dando clic en el símbolo de + y en el ítem que se agrega, despliega el combo y selecciona el RedMaterial que acabamos de crear. Verás como inmediatamente el personaje toma color rojo.

Captura del RedCharacterBlueprint en el modo Components después de settear el RedMaterial como Material del Mesh

Captura del RedCharacterBlueprint en el modo Components después de settear el RedMaterial como Material del Mesh

 

Ahora repite todo el proceso para configurar el GreenCharacterBlueprint, crea asígnale a este un material de color verde.

Por último, agrega el nivel cerca del PlayerStart una instancia del RedCharacter y una instancia del GreenCharacter y elimina los obstáculos que trae el nivel de esta plantilla top-down. Con eso ya tendremos nuestra escena lista.

Captura del nivel preparado después de eliminar los obstáculos y agregar las instancias del RedCharacterBlueprint y GreenCharacterBlueprint

Captura del nivel preparado después de eliminar los obstáculos y agregar las instancias del RedCharacterBlueprint y GreenCharacterBlueprint

 

Como comentamos, lo que queramos es implementar un mecanismo que nos permita notificar a los otros dos personajes (Green y Red) cuando ocurre un evento determinado en el juego, en este caso, cuando nuestro personaje muere, y para esto usaremos los Delegates.

Implementando un Delegate desde C++ en Unreal Engine

En Unreal Engine 4 podemos declarar dos tipos de Delegates, Single-cast y Multi-cast. Los Single-cast nos van a permitir notificar solo a un objeto determinado de nuestro juego, por ejemplo, si quisiéramos que solo el RedCharacter se entere de la muerte de nuestro personaje. Por otra parte, los Multi-cast nos permiten notificar a todas las clases que queramos. En la mayoría de los casos usaremos Multi-cast, ya que estos también podemos usarlo para notificar a solo una clase, así que vamos directamente a ver los multi-cast, y comentamos en cada punto la diferencia a los Single-cast.

Como dijimos al inicio, el objetivo de los Delegates es permitirnos ejecutar un método determinado de otro objeto cuando ocurre algún evento del juego, incluso sin tener acceso directo al método de ese objeto, pero como sabes, los métodos pueden tener distintas estructuras. Un método puede no retornar ningún parámetro ni recibir parámetros. Puede también no retornar nada, pero recibir un parámetro o dos o los que sean y así tenemos muchas variantes. Pues para declarar los Delegates en C++ el compilador de Unreal nos brinda algunos macros específicos según el tipo de método que vayamos a ejecutar al lanzar el delegate.

Function signature Declaration macro
void Function() DECLARE_MULTICAST_DELEGATE( DelegateName )
void Function( ) DECLARE_MULTICAST_DELEGATE_OneParam( DelegateName, Param1Type )
void Function( , ) DECLARE_MULTICAST_DELEGATE_TwoParams( DelegateName, Param1Type, Param2Type )
void Function( , , … ) DECLARE_MULTICAST_DELEGATE_Params( DelegateName, Param1Type, Param2Type, … )
Function() DECLARE_MULTICAST_DELEGATE_RetVal( RetValType, DelegateName )
Function( ) DECLARE_MULTICAST_DELEGATE_RetVal_OneParam( RetValType, DelegateName, Param1Type )
Function( , ) DECLARE_MULTICAST_DELEGATE_RetVal_TwoParams( RetValType, DelegateName, Param1Type, Param2Type )
Function( , , … ) DECLARE_MULTICAST_DELEGATE_RetVal_Params( RetValType, DelegateName, Param1Type, Param2Type, … )

Para nuestro ejemplo usaremos el caso más simple que es ejecutar un método sin valor de retorno ni parámetros, pero al final la lógica es la misma para cada caso.

En el caso de la declaración de los Delegates Single-cast, la lógica es la misma, solo que se usa una variación del macro sin la palabra MULTICAST, por ejemplo para el primer caso sería DECLARE_DELEGATE

Lo primero que vamos a hacer es declarar este delegate. Abre el .h de la clase del PlayerCharacter y antes de la declaración de la clase agrega la declaración del Delegate de la siguiente forma:

DECLARE_MULTICAST_DELEGATE( FMulticastDelegateSample );

En este caso lo que acabamos de declarar es un multi-cast delegate al que se le pueden “bindiar“ métodos que no retornan nada ni reciben parámetros. El próximo paso es agregar una instancia de este delegate en la clase que lanzaría el evento. En este caso es nuestro personaje protagónico así que agrega como variable de clase del personaje, lo siguiente:

FMulticastDelegateSample MulticastDelegate;

Ahora solo nos queda ejecutar este delegate en el momento que queramos. En este caso vamos a crear un método Die en el Player, este método se llamará cuando el personaje muera y en ese momento se lazará el delegate. Para adornar un poco el momento de la muerte, reproduciremos una animación dentro de este método Die así que agrega también una instancia de tipo UAnimationAsset para cargar el Asset de animación desde el Editor, como hemos hecho en tutoriales anteriores.

Agrega lo siguiente en el .h

/** Asset de la animacion de muerte */
UPROPERTY(EditDefaultsOnly, Category = Animations, meta = (AllowPrivateAccess = "true"))
UAnimationAsset *DeathAnimation;

UFUNCTION(BlueprintCallable, Category=Default)
void Die();

Ahora pasa al .cpp y agrega la implementación del método

void AUE4SampleCharacter::Die()
{
    //Reproduce la animación de muerte
    if(DeathAnimation)
    {
        GetMesh()->PlayAnimation(DeathAnimation, false);    
    }
    
    //Lanza el Delegate para notificar en todos los objetos que bindiaron método a este delegate que el PlayerCharacter acaba de morir
    MulticastDelegate.Broadcast();
}

Como puedes ver, es súper simple lanzar el delegate. Suficiente con llamar al método Broadcast(). Una vez que se llama este método se ejecutará cada uno de los métodos que fueron bindiados a él en los objetos correspondientes.

Dos cosas a tener en cuenta con el Broadcast() es que lo podemos llamar incluso si al delegate no se le ha bindiado ningún método, como es en este preciso momento, y otra cosa importante es que en caso que tengamos bindiado más de un método, el orden de ejecución de estos no es garantizado, así que tenlo en cuenta y no implementes ninguna lógica que pueda depender de este orden.

Para el caso de los Single-cast, para llamarlos tenemos los métodos Execute(), ExecuteIfBound() y IsBound().

Con esto que hemos hecho hasta ahora ya tenemos creado y ejecutamos el delegate en el momento que queremos, pero no hemos bindiado a él nada, por lo que al ejecutar el Broadcast() simplemente no pasará nada. Así que vamos a implementar el método que queremos llamar en el GreenCharacter y bindiarlo al delegate.

Bindiando métodos al multi-cast delegate desde C++

Modifica la clase GreenCharacter para que te quede de la siguiente forma:

//----------------------------
// GreenCharacter.h
//----------------------------

#pragma once

#include "GameFramework/Character.h"
#include "GreenCharacter.generated.h"

UCLASS()
class UE4SAMPLE_API AGreenCharacter : public ACharacter
{
	GENERATED_BODY()

    /** Asset de la animacion de muerte */
    UPROPERTY(EditDefaultsOnly, Category = Animations, meta = (AllowPrivateAccess = "true"))
    UAnimationAsset *DeathAnimation;

    /** Método que se llama en este clase, mediante el MulticastDelegate del PlayerCharacter cuando este muere */
    void OnPlayerCharacterDie();

    void Die();
    
    virtual void BeginPlay() override;

};

//----------------------------
// GreenCharacter.cpp
//----------------------------

#include "UE4Sample.h"
#include "GreenCharacter.h"
#include "UE4SampleCharacter.h"
#include "Engine.h" //Para poder usar el GetWorldTimerManager().SetTimer

void AGreenCharacter::BeginPlay()
{
    Super::BeginPlay();
    
    //Obtiene la referencia del PlayerCharacter
    AUE4SampleCharacter* PlayerCharacter = Cast<AUE4SampleCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
    
    //"Bindea" el método OnPlayerCharacterDie de esta clase al delegate que se lanza cuando el PlayerCharacter muere.
    PlayerCharacter->MulticastDelegate.AddUObject(this, &AGreenCharacter::OnPlayerCharacterDie);
}

/** Método que se llama en este clase, mediante el MulticastDelegate del PlayerCharacter cuando este muere */
void AGreenCharacter::OnPlayerCharacterDie()
{
    //Delay de 1 segundo antes de llamar al método Die de esta clase
    GetWorldTimerManager().SetTimer(this, &AGreenCharacter::Die, 1.0, false);
}

void AGreenCharacter::Die()
{
    //Reproduce una animación de muerte
    if(DeathAnimation)
    {
        GetMesh()->PlayAnimation(DeathAnimation, false);
    }
}

El método OnPlayerCharacterDie, que es el método que vamos a bindiar al delegate del PlayerCharacter, simplemente ejecuta un delay y al segundo llama al método Die, que al igual que el método Die del PlayerCharacter, lo que hace es reproducir una animación de muerte. Cuando el PlayerCharacter muera y se notifique a este objeto, el GreenCharacter esperará un segundo (para asimilar la noticia de que su compañero murió :( . . . ) y también morirá . . . Un poco trágica la historia ahora que lo pienso, así que en tu juego usa los delegates para cosas más alegres :)

OnPlayerCharaterDie será el método que se llamará automáticamente cuando el Delegate que creamos en el Character se dispare, pero para esto tenemos que bindiar el método al delegate y esto vamos a hacerlo en el BeginPlay.

Cuando necesitamos bindiar el método de alguna clase a un delegate determinado, una buena idea es hacerlo en el BeginPlay de esos actores, para ya no tener que preocuparnos de eso en el transcurso del juego. Sobrescribe el BeginPlay de esta clase para que te quede de la siguiente forma:

void AGreenCharacter::BeginPlay()
{
    Super::BeginPlay();
    
    //Obtiene la referencia del PlayerCharacter
    AUE4SampleCharacter* PlayerCharacter = Cast<AUE4SampleCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
    
    //"Bindea" el método OnPlayerCharacterDie de esta clase al delegate que se lanza cuando el PlayerCharacter muere.
    PlayerCharacter->MulticastDelegate.AddUObject(this, &AGreenCharacter::OnPlayerCharacterDie);
}

En el BeginPlay de este character simplemente obtenemos la referencia al Character accedemos a la instancia del delegate y mediante el método AddUObject bindiamos el método que queremos ejecutar. El primer parámetro que recibe el AddUObject es la instancia de la clase que contiene el método que vamos a ejecutar, en este caso esta misma clase, por eso el uso de “this” y el segundo es el método que se va a ejecutar. Si te fijas es muy parecido a lo que hacemos al bindiar los métodos a las entrada del usuario. En ese caso lo que hacemos es justamente esto mismo, bindiar un método a un delegate que se llama cuando el Engine detecta la entrada del usuario.

En este punto vale aclarar que por lo general vamos a bindiar a un delegate un método de un instancia UObject, pero el framework también nos brinda los métodos Add(), AddStatic(), AddRaw() y AddSP() para bindiar al delegate otro tipo de funciones, como por ejemplo punteros a funciones globales. Para una referencia más completa de cada uno de los casos puedes darte una vuelta por: docs.unrealengine.com

Algo a tener en cuenta es que de la misma forma que agregamos métodos al delegate, podemos eliminarlos. Esto nos puede ser útil si a partir de determinada condición ya no queremos que se notifique mas a un objeto de determinado evento. Para esto podemos usar los métodos del delegate: Remove() y RemoveAll()

Para bindiar métodos en el caso de los Single-cast delegates tenemos los mismos métodos pero sustituyendo la palabra Add por Bind. Por ejemplo, para los multi-cast tenemos AddUObject y para los single cast tenemos BindUObject. Para eliminar tenemos el método UnBind()

Listo !! . . . esto es todo lo que necesitamos. Guarda, compila y abre el editor.

Para facilitar la muerte de nuestro personaje (el evento que queremos notificar en todos los otros personajes) vamos a forzarla a que suceda cuando se toque la barra espaciadora, no es muy real pero nos permitirá centrarnos en el asunto que de verdad nos interesa en este tutorial. Abre el Project Settings y en la sección Input agrega una entrada de tipo Action, ponle de nombre KillPlayerCharacter y que se active con la barra espaciadora

Ahora abre el Blueprint MyCharacter. Primero en el modo Default carga para la propiedad DeathAnimation una animación de muerte cualquiera que puedes obtener haciendo un Retarget de cualquiera de las animaciones que vienen en el AnimStarterPack.

Pasa al modo Graph y agrega lo siguiente para que cuando se presione la barra espaciadora se llame al método Die que implementamos en el PlayerCharacter.

Captura del Event Graph del PlayerCharacter. Ejecuta el método Die cuando se detecta el InputAction KillPlayerCharacter (Barra Espaciadora)

Captura del Event Graph del PlayerCharacter. Ejecuta el método Die cuando se detecta el InputAction KillPlayerCharacter (Barra Espaciadora)

 

Hecho eso, compila, guarda y dale Play al juego. Presiona la barra espaciadora para forzar la muerte de nuestro Character y verás que un segundo después de la muerte del personaje protagónico, el GreenCharacter también cae al suelo. Lo que nos demuestra que se llamó el método correspondiente en esa instancia en el momento preciso. Genial verdad !!??

 

Captura de dos momentos del juego. El primer cuadro, al iniciar el juego. El segundo cuadro, al tocar la barra espaciadora el PlayerCharacter muere, lanza el Delegate de que murió, es notificado el GreenCharacter mediante el método OnPlayerCharacterDie, inicia un Timer por 1 segundo y después llama al método Die, que hace que el GreenCharacter muera también.

Captura de dos momentos del juego. El primer cuadro, al iniciar el juego. El segundo cuadro, al tocar la barra espaciadora el PlayerCharacter muere, lanza el Delegate de que murió, es notificado el GreenCharacter mediante el método OnPlayerCharacterDie, inicia un Timer por 1 segundo y después llama al método Die, que hace que el GreenCharacter muera también.

 

Bindiando más de un método de distintos objetos a un mismo Delegate

Como dijimos al inicio, la ventaja que tenemos con los delegate Multi-cast es que no estamos limitados a bindiar un solo método, sino que podemos bindiar todos los métodos que queramos de distintas clases. Por ejemplo, supongamos que también queremos que cuando muera nuestro personaje “se entere“ el RedCharacter y que ejecute otra lógica.

Pues para esto no tenemos que hacer nada en espacial. Simplemente implementar el método que queramos en el RedCharacter y bindiarlo al delegate del PlayerCharacter en el BeginPlay, como mismo hicimos con el GreenCharacter:

//----------------------------
// RedCharacter.h
//----------------------------

#pragma once

#include "GameFramework/Character.h"
#include "RedCharacter.generated.h"

UCLASS()
class UE4SAMPLE_API ARedCharacter : public ACharacter
{
	GENERATED_BODY()
	
    /** Material a usar en el Mesh de este Character cuando el PlayerCharacter muere */
    UPROPERTY(EditDefaultsOnly, Category = Materials, meta = (AllowPrivateAccess = "true"))
    UMaterial *PlayerCharacterDeadMaterial;

    /** Método que se llama mediante el MulticastDelegate del PlayerCharacter cuando este muere */
    void OnPlayerCharacterDie();

    virtual void BeginPlay() override;
};

//----------------------------
// RedCharacter.cpp
//----------------------------

#include "UE4Sample.h"
#include "RedCharacter.h"
#include "UE4SampleCharacter.h"

void ARedCharacter::BeginPlay()
{
    Super::BeginPlay();
    
    //Obtiene la referencia del PlayerCharacter
    AUE4SampleCharacter* PlayerCharacter = Cast<AUE4SampleCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
    
    //"Bindea" el método OnPlayerCharacterDie de esta clase al delegate que se lanza cuando el PlayerCharacter muere.
    PlayerCharacter->MulticastDelegate.AddUObject(this, &ARedCharacter::OnPlayerCharacterDie);
}

/** Método que se llama mediante el MulticastDelegate del PlayerCharacter cuando este muere */
void ARedCharacter::OnPlayerCharacterDie()
{
    if(PlayerCharacterDeadMaterial)
    {
        //Cambia "al vuelo" el primer Material del Mesh de este personaje
        GetMesh()->SetMaterial(0, PlayerCharacterDeadMaterial);
    }
}

Este método lo que hará será cambiar el Material del Mesh del RedCharacter al que carguemos desde el Editor en la propiedad PlayerCharacterDeadMaterial. Para el ejemplo yo preparé un Material de color negro, de esta forma cuando el RedCharacter sea notificado de la muerte del PlayerCharacter este se pondrá negro al instante.

Guarda, compila y lanza el juego. Toca la barra espaciadora y verás como el RedCharacter cambia a color negro al instante y el GreenCharacter, al pasar un segundo, cae al suelto.

 

Captura del juego después que muere el PlayerCharacter. Son notificados ambos personajes. GreenCharacter muere al segundo y RedCharacter cambia su color a negro.

Captura del juego después que muere el PlayerCharacter. Son notificados ambos personajes. GreenCharacter muere al segundo y RedCharacter cambia su color a negro.

 

Muy bien, con este ejemplo hemos podido ver el enorme potencial de los delegates y la forma de usarlos desde C++, pero como una de las potencialidades más grande que tiene el Unreal Engine es su mecanismo de VisualScripting mediante los blueprints, vamos a ver como implementar este mismo tipo de mecanismos desde los blueprints.

Introducción a los Event Dispatchers en Unreal Engine 4.

Como sabes, gracias a “la magia“ que ha logrado el equipo de Epic con los Blueprints para abstraernos de toda la complejidad que puede tener para muchos el asunto de C++, todo esto lo podemos hacer súper fácil y rápido también desde Blueprints, aquí los nombres varían un poco pero el concepto es el mismo.

En los blueprints podemos crear Event Dispatchers. Los Event Dispatchers serán básicamente los delegates, estos Event Dispatchers solamente los podemos ejecutar o bindiar a ellos eventos de otros blueprints.

Para ver un ejemplo práctico, vamos a plantearnos algo simpático. Vamos a suponer que nuestro personaje puede aumentar su fuerza a un nivel extremo y en ese caso se vuelve negro, en el punto en el que nuestro personaje llega a este nivel, el RedCharacter que sería su enemigo, se hecha a correr :).

Agrega otra Action al Project Settings/Input y llámalo Activate Super Power (o como prefieras).

En el Event Graph del Character fíjate que en el panel My Blueprint, desde el que podemos agregar variables y funciones también tenemos la opción para crear un Event Dispatcher. Da clic aquí para crear uno y llámalo SuperPowerActivated.

ue4_tuto9_img04.0

Una vez creado, desde el Panel Detalles, en la sección Inputs, podemos agregar parámetros de entrada según el método que queramos ejecutar al llamar a este Event Dispatcher, en nuestro caso no necesitamos ningún parámetro.

Ahora, modifica el Event Graph de la siguiente forma:

ue4_tuto9_img04.1

Esta es la lógica que se ejecutará cuando aumentemos de poder. A modo de demostración simplemente cambiamos nuestro color a negro.

Es en este preciso momento es cuando queremos notificar que aumentamos nuestro poder, para esto arrastra desde el panel My Blueprint hasta el Event Graph, el Event Dispatcher que creamos anteriormente. Al soltarlo te mostrará un menú contextual con las distintas opciones relacionadas con ese Event Dispatchers, selecciona de ahí la opción Call.

Captura del EventGraph del MyCharacter después de agregar el llamado del Event Dispatcher una vez que el personaje incrementa su poder (cuando se detecta la entrada de nombre ActivateSuperPower)

Captura del EventGraph del MyCharacter después de agregar el llamado del Event Dispatcher una vez que el personaje incrementa su poder (cuando se detecta la entrada de nombre ActivateSuperPower)

 

Listo, este es el “sinónimo“ del Broadcast de C++. Con esto todos los métodos que se hayan bindiado a este Event Dispatchers se llamarán. Pero, como mismo vimos en C++, si no bindiamos ningún evento a este Event Dispatcher no pasará nada, así que vamos a ello.

Abre el RedCharacterBlueprint crea un Custom Event de nombre OnPlayerCharacterIncreasePower e implementa en él el siguiente algoritmo.

A partir del Actor Location obtenemos un vector 1000 unidades detrás del personaje y el aplicamos un AI Move To que hará que este personaje salga corriendo hacia ese punto.

A partir del Actor Location obtenemos un vector 1000 unidades detrás del personaje y el aplicamos un AI Move To que hará que este personaje salga corriendo hacia ese punto.

 

Listo ya tenemos el evento que queremos ejecutar cuando el PlayerCharacter aumente su poder, ahora solo nos queda bindiarlo al Event Dispatcher del PlayerCharacter.

Como mismo hicimos en C++, vamos a implementar en el Begin Play el binding. Necesitamos acceder al PlayerCharacter que es donde tenemos el Event Dispatcher, una vez que tengamos el PlayerCharacter casteado a MyCharacter podemos seleccionar Bind Event To . . . que nos permitirá bindiar un evento a cualquiera de los Event Dispatchers que existan en el PlayerCharacter. Fíjate que podemos Bindiar un evento al SuperPowerActivated que creamos, pero también podemos bindiar evento a muchos otros momentos importantes, como el OnDestroyed, el OnTakeAnyDamage y muchísimos otros eventos que nos brinda el Engine ya por defecto, así que ya sabes, si en algún momento necesitas lanzar un evento cuando un Actor es destruido, por ejemplo, puedes bindiar tu evento al OnDestroyed. Esto es algo que se usa mucho para incrementar puntos, o hacer respawn de otros personajes, o si es el OnDestroyed del PlayerCharacter, para dar el Game Over.

Retomando nuestro ejemplo, el EventGraph del RedCharacterBlueprint nos quedaría así:

Captura del EventGraph del RedCharacterBlueprint después de implementar el Event Begin Play, donde se obtiene la referencia al MyCharacter y se bindea el evento OnPlayerCharacterIncreasePower al Event Dispatchers que se llama cuando el PlayerCharacter aumenta de poder.

Captura del EventGraph del RedCharacterBlueprint después de implementar el Event Begin Play, donde se obtiene la referencia al MyCharacter y se bindea el evento OnPlayerCharacterIncreasePower al Event Dispatchers que se llama cuando el PlayerCharacter aumenta de poder.

 

Listo ¡!! . . . guarda los cambios y dale Play al juego. Toca la tecla para activar el super poder, veras como nuestro personaje se vuelve negro, y el RedCharacter sale corriendo como todo un cobarde :)

ue4_tuto9_img08

Conclusión

Este tutorial ha sido un poco más corto que los de costumbre pero espero que haya servido para darte un acercamiento a como trabajar con Delegates en Unreal Engine 4 y que puedas explotar a partir de ahora esta fenomenal vía que tenemos de notificar a los distintos Actors de nuestro juego cuando un evento determinado ocurre.

Para profundizar en los Delegates desde C++ puedes darte una vuelta por la documentación oficial

. . . Y esto es todo por hoy. Seguiremos viendo nuevas cosas de Unreal Engine 4 en próximos tutoriales así que no te vayas muy lejos. También puedes seguirme en Twitter (@nan2cc) y así te dejo saber cuando tengamos un nuevo tutorial. Mientras, me encantaría escuchar tus comentarios o temáticas que quisieras que tocara en próximos tutoriales. Hasta la próxima, bye 😉

Implementando un AI Character con un arma en UE4

Hola, soy Dariel de la Noval (@darielns), Lead Programmer en Spissa Software Solutions. He estado siguiendo los tutoriales de @nan2cc y la aceptación que han estado teniendo y me he embullado a sumarme en esta serie de tutoriales sobre el desarrollo de juegos con Unreal Engine 4, aquí voy con mi primer tuto, ya me dirás que te parece.

En este tutorial vamos a unir muchas de las cosas que ya hemos visto en tutoriales anteriores para agregar un enemigo a nuestro juego. Este enemigo estará patrullando una zona del nivel con un arma, al acercarnos, comenzará a dispararnos hasta matarnos. De igual forma, ya con el arma que configuramos para nuestro personaje en el tutorial pasado, podremos defendernos de estos ataques.

NOTA: Este tutorial ha sido desarrollado con Unreal Engine 4.4.3, si estás trabajando con otra versión puede que encuentres algunas diferencias, ya que el Engine está en constante actualización. De ser así, déjame tus comentarios al final del post y buscamos juntos la solución.

Importando los recursos necesarios al proyecto

La mayor parte de este tutorial toca muchos temas de Inteligencia Artificial que ya vimos a fondo en tutoriales anteriores, por este motivo en vez de desarrollar esta parte de nuevo, las importaremos directamente al proyecto.

Vamos a comenzar importando los recursos necesarios para tener rápidamente un personaje controlado por IA patrullando una zona del nivel y que detecte cuando nos acercamos a esa zona, como mismo hicimos en los tutoriales de IA anteriores.

Descarga los recursos necesarios desde aquí, al descomprimir el .zip tendrás lo siguiente:

Dentro de la carpeta M1Garand tendrás el FBX del modelo del arma que usará el enemigo. Este modelo al igual que el del arma del Player también fue descargada desde tf3dm.com

Dentro de la carpeta AIEnemy tendrás los siguientes recursos:

  • AIEnemyAnimBlueprint: Animation Blueprint del enemigo. Inicialmente solo contiene la lógica para las animaciones de reposo y caminando.
  • AIEnemyBehaviorTree: Behavior Tree (BT) encargado de la AI del enemigo. Inicialmente solo contiene el procesamiento para patrullar una zona y detectarnos si nos acercamos a esa zona.
  • AIEnemyBlackboard: BlackBoard asociado al BT del enemigo. Solo contiene 3 elementos: TargetPointNumber, TargetPointPosition e IsActorDetected. Los dos primeros son utilizados en el algoritmo de patrullado y el tercero en el algoritmo de chequear cuando nos acercamos.
  • AIEnemyCharacterBlueprint: Blueprint del enemigo.
  • AIEnemyController: Controlador del enemigo. Inicialmente solo contiene la asignación del BT.
  • AIEnemyTargetPoint: Target Point que se utilizara para definir los puntos claves en la zona de patrullado del enemigo.
  • CheckNearbyEnemy: Task del BT encargado de chequear cuando el player se acerca al enemigo.
  • IdleWalkBlendSpace1D: BlendSpace utilizado para hacer un blend entre la animación de Idle y Walk del enemigo.
  • UpdateNextTargetPointTask: Task del BT con la lógica para seleccionar el siguiente punto clave en el recorrido de patrulla del enemigo.

Para importar los recursos que están dentro de la carpeta AIEnemy, primero asegúrate de tener en tu proyecto el AnimStarterPack. Después, abre la carpeta donde se encuentra ubicado tu proyecto desde el explorador de ficheros del sistema operativo y copia dentro de Content, la carpeta AIEnemy.

Para el caso del FBX del arma que usará el enemigo, este impórtalo desde el FBX Import del Unreal como ya hemos hecho antes. Una vez que importes el arma, ábrela en el StaticMesh Editor, y como mismo hicimos para el arma del Player en el tutorial pasado, créale un Socket de nombre FireSocket en la punta del cañón.

Captura del StaticMesh Editor con el arma del enemigo cargada después de la creación del FireSocket

Captura del StaticMesh Editor con el arma del enemigo cargada después de la creación del FireSocket

 

Abre el editor y verás en el Content Browser los recursos importados. Puedes tomarte unos minutos para que le des un vistazo a cada elemento.

Por último, desde tutoriales anteriores estamos usando el AnimStarterPack que puedes descargar del MarketPlace. Pues, el personaje que viene en este paquete es el que usaremos como enemigo, pero como tendrá un arma en su mano, necesitamos crearle un socket como mismo hicimos para el Player en el tutorial anterior.

Abre el HeroTPP que viene en el AnimStarterPack y créale un socket en la mano, puedes usar como preview el arma que importamos para poder posicionar correctamente el socket, llámalo HandSocket.

Captura del Persona Editor después de crear el HandSocket para el enemigo

Captura del Persona Editor después de crear el HandSocket para el enemigo

 

Preparando la zona a patrullar

Prepara en el nivel la zona que quieres que patrulle el enemigo y enciérrala en un objeto de tipo NevMeshBoundVolume.

Agrega al nivel dos AIEnemyTargetPoints, estos los usaremos para definir el recorrido del personaje. Recuerda que nuestro juego es un scroll-side y nos movemos básicamente en un solo eje, por lo que el enemigo también se tendrá que mover por un solo eje, así que asegúrate de que los dos AIEnemyTargetPoints queden alineados y paralelos a la posición del PlayerStart.

A cada uno de estos AIEnemyTargetPoints hay que definirle el orden que representarán en el recorrido que estará haciendo el enemigo. Selecciona el primero y en el panel de Detalles, en la sección Default, dale valor de 0 a la variable Position. Selecciona el otro punto y dale valor de uno.

Captura del ViewPort de la escena con los dos TargetPoints agregados y la configuración correspondiente para la propiedad Position

Captura del ViewPort de la escena con los dos TargetPoints agregados y la configuración correspondiente para la propiedad Position

 

Por último, agrega al nivel dentro de esta zona, el AIEnemyCharacter que acabamos de importar y que tendremos en la carpeta AIEnemy. Recuerda ponerlo siempre alineado al Player Start.

Captura del ViewPort de la escena después de agregar el AIEnemyCharacter dentro de la zona que va a patrullar

Captura del ViewPort de la escena después de agregar el AIEnemyCharacter dentro de la zona que va a patrullar

 

Compila, lanza el juego y muévete hacia el enemigo. Verás cómo estará rondando de punto a punto y en cada punto esperará 2 segundos. Cuando detecte que estamos cerca de él se imprime en la pantalla, a modo de log, el mensaje: Detectando Enemigo Cercano!!

Te recuerdo que si tienes algún problema con esta parte por la que hemos pasado bastante rápido, dale un vistazo a este tutorial donde implementamos toda esta lógica de IA paso a paso.

Preparando el arma que usará el enemigo

Como ves, con lo hecho hasta ahora, tenemos el enemigo patrullando una zona del nivel pero por las animaciones que tiene puedes darte cuenta que le está faltando su arma. Vamos a agregar un arma en las manos de este personaje siguiendo el mismo principio que usamos en el tutorial pasado. En ese tutorial, colocábamos algunas armas en el escenario para que el Player las recogiera, pero si recuerdas, nunca creamos un blueprint de estas armas, implementamos toda su lógica desde C++ y después la agregábamos al nivel desde la sección All Classes.

Eso está bien, pero en el mundo Unreal una muy buena práctica es crear un Blueprint a partir de las clases que tengamos implementadas en C++. Esto nos permite, por ejemplo, modificar varios parámetros de la clase a nivel visual, una sola vez. Por ejemplo, imagínate que queramos tener en distintas zonas del nivel varias armas del mismo tipo. Si creamos un blueprint a partir de la clase Weapon que implementamos en C++, le definimos el Mesh y las propiedades que queramos una sola vez desde el blueprint, y después agregamos las instancias de este blueprint al nivel.

Vamos a crear un blueprint a partir de la clase Weapon que implementamos en C++. En el tutorial pasado implementamos toda la lógica del arma que usa el personaje, la SPAS12. Si quisiéramos usar la misma lógica de esta arma para el arma del enemigo, pudiéramos crear el blueprint a partir de esta clase que ya hicimos, pero por ejemplo, supongamos que en el arma de este enemigo la lógica del disparo sea distinta. En este caso creamos la clase para que herede de Weapon que es la clase base de las armas, y entonces implementamos en ella las funcionalidades específicas de esta arma.

Dentro de la carpeta Weapons crea un nuevo Blueprint que herede de la clase Weapon que implementamos en el tutorial pasado y ponle como nombre M1GarandWeaponBlueprint. Ábrelo, y en la sección Components, muévete hasta la propiedad WeaponMesh y en el panel de detalle dentro de la sección Static Mesh selecciona el Mesh del rifle que importamos al inicio.

Captura del M1GarandWeaponBlueprint que acabamos de crear.

Captura del M1GarandWeaponBlueprint que acabamos de crear.

 

Compila, salva los cambios y cierra este Blueprint.

Equipando al enemigo con un arma

Para el Player, en el tutorial pasado, acoplábamos el arma al socket una vez que este la recogía del escenario. Pero en este caso, desde el inicio del juego el enemigo tendrá equipada su arma. Para lograr esto le agregaremos un nuevo componente desde el Blueprint, el Child Actor Component.

Abre el AIEnemyCharacterBlueprint y desde la sección Components, despliega el combo Add Component, y selecciona Child Actor.

tuto8_imagen_06

El Chilld Actor nos permite agregar como componente de un Actor, otro Actor cualquiera. En este caso lo usaremos para agregarle el arma.

En este punto me gustaría comentarte otro componente parecido, el StaticMesh. Este nos permite agregar un StaticMesh cualquiera como componente hijo del Actor. Un ejemplo de su uso pudiera ser si este personaje tuviera un sombrero y el sombrero fuese un elemento independiente del modelo original. En este caso podemos agregarlo como StaticMesh al personaje, y después anclarlo a un socket determinado, como mismo haremos con el arma.

Entonces, al ChildActor que agregamos ponle como nombre WeaponComponent, selecciónalo y muévete en el panel de detalles del componente hasta la propiedad Child Actor Component y aquí selecciónale el blueprint del arma que preparamos para el enemigo.

7

Solamente nos queda anclar el arma al socket en la mano del personaje y esto lo haremos en el Construction Script. En el modo Graph selecciona la pestaña Construction Script, en la que inicialmente solo tenemos el nodo Construction Script.

Ya vimos en tutoriales pasados que el Construction Script se ejecuta en la inicialización del objeto, y es el lugar que tenemos para implementar las funcionalidades en el momento de la creación del Actor.

Vamos a anclar el arma al socket de la mano, y para ello utilizaremos el nodo Attach To. Este nodo permite adjuntar un elemento determinado a otro y recibe los siguientes parámetros:

Target: Este es el elemento que se desea adjuntar. En este caso, el componente WeaponComponent.
In Parent: A quien vamos a adjuntar dicho target. En este caso, al mesh del enemigo.
In Socket Name: Socket al cual se adjuntará el elemento Target. En este caso será al socket de la mano (HandSocket ).
Attach Type: Este será el modo en que se adjuntara. En este caso, Snap To Target.

Agrega al blueprint el nodo Attach To y asigna en cada parámetro los elementos correspondientes. Por último, enlaza el nodo Construction Script al Attach To. Te quedará de la siguiente forma:

Construction Script en el AIEnemyBlueprint para acoplar el arma en las manos de este personaje

Construction Script en el AIEnemyBlueprint para acoplar el arma en las manos de este personaje

 

Salva los cambios y compila. Cambia al modo componentes y podrás observar como ahora el rifle sale en las manos de este personaje.

Captura de la pre visualización del personaje enemigo después de compilar el Construction Script donde se acopla el arma al HandSocket

Captura de la pre visualización del personaje enemigo después de compilar el Construction Script donde se acopla el arma al HandSocket

 

Al correr el juego notarás que ya el enemigo tendrá el arma equipada y se moverá con ella en todo momento.

Implementado la lógica en el enemigo para que cuando nos detecte nos comience a disparar

Ya tenemos a nuestro enemigo patrullando con su arma una zona del nivel, pero a pesar de que el Player se le acerca, no le dispara, así que vamos a implementar la lógica para cuando nos detecte nos comience a disparar. Para esto crearemos un nuevo Task en el BT de este personaje, este Task se ejecutará cuando el enemigo detecte que tiene al Player cerca y básicamente lo que hará es rotarse hacia él y poner en true la variable que usaremos desde el Animation Blueprint para comenzar la animación del disparo.

Abre el AIEnemyAnimBlueprint y crea una nueva variable, dale de nombre IsShooting y de tipo bool. Guardar y cierra este Blueprint.

Crea un nuevo Task para el BT del enemigo y ponle como nombre RotateAndShoot y en él implementa el siguiente algoritmo.

RotateAndShoot Task, encargado de rotar el enemigo en la dirección del Player y pasar a true la variable para iniciar la animación de disparo

RotateAndShoot Task, encargado de rotar el enemigo en la dirección del Player y pasar a true la variable para iniciar la animación de disparo

 

Este Task se ejecutará cuando se detecte el personaje, pero fíjate que el enemigo puede detectar al Player estando en su dirección o no, por lo que primero tenemos que garantizar que este rote en la dirección del personaje antes que nos dispare. Esto lo logramos con un poco de matemática básica, rotando el vector de Location del enemigo y el vector de location del Player y a partir de ese vector resultante creamos un Rotator que afecte solo el eje X con el nodo Rotation From XVector y con el Rotator resultante actualizamos la rotación del enemigo. Hecho esto solo nos resta pasar a true la variable IsShooting para que comience la animación de disparo.

Este Task también tiene otro detalle. Si recuerdas cuando vimos los tema de IA a fondo comentamos que los Task puede tener 3 estados. Primero, que termina su ejecución satisfactoriamente, segundo, que termina su ejecución NO satisfactoriamente y un tercer estado que es mantener el Task en “pendiente“. Este es el caso en el que el Task sea una acción que demorará su ejecución, como es el caso. Recuerda que el task se ejecuta, el personaje rota hacia nosotros y comienza a disparar. Para hacer esto, y evitar que mientras el BT del personaje caiga en un ciclo sobre este Task mientras está disparando, fíjate que no llamamos al Finish Execute del Task.

Bien, entonces, como necesitamos que este Task se ejecute solamente si el personaje detecta que tiene al Player cerca, crearemos un nuevo Decorator en el BT para controlar esto. Arrastra del borde inferior del CheckNearbyEnemy y crea un nuevo Decorator de tipo Blackboard. Selecciona el decorator y en la sección Flow Control de panel de Detalles, en el atributo Observer aborts selecciona Both. En este caso lo que queremos es que inmediatamente que IsActorDetected esté en false se pase a ejecutar la otra rama, para que continúe como vigilante.

Selecciona el Decorator y en la sección Blackboard, en el atributo BlackBoard Key, selecciona IsActorDetected para definirle que este es el key del blackboard que queremos comprobar y en Key Query selecciona Is Set para definirle el tipo de condición que queremos comprobar sobre el IsActorDetected.

Por último arrastra del selector que tiene este Decorator y conéctalo al RotateAndShoot.

Captura del BT del enemigo después de agregar el RotateAndShoot y el decorator para controlar su ejecución.

Captura del BT del enemigo después de agregar el RotateAndShoot y el decorator para controlar su ejecución.

 

Listo ¡!! Guarda, compila, lanza el juego y muévete cerca del enemigo. Cuando este te detecta, se detiene de su tarea de patrullar, y en ese punto la variable IsShooting toma valor de true. Lo que nos queda es preparar la lógica de la animaciones para cuando esta variable esté en true, se comience a reproducir la animación del disparo.

Configurando animación de disparo del enemigo

Como las acciones de este enemigo son bastante básicas (solamente camina de un lado a otro y cuando nos detecta, se detiene ahí mismo y comienza a disparar desde el lugar) la lógica de la animación de disparar la podemos implementar como un nodo nuevo del StateMachine de este personaje, a este estado se pasará una vez que la variable variable IsShooting sea true y cuando sea falso se retornará a la animación de Idle/Walk.

Abre el StateMachine desde el AIEnemyAnimBlueprint y agrega el nodo Shooting con las transiciones correspondientes, fíjate que dentro de Shooting lo que hacemos es reproducir la animación Fire_Shotgun_Ironsights. Te quedará de la siguiente forma.

Maquina de estado del enemigo después de agregar el estado Shooting

Maquina de estado del enemigo después de agregar el estado Shooting

 

Salva los cambios, compila, lanza el juego y camina hasta el enemigo. Verás que cuando te le acercas comienza a ejecutar la animación de disparar, pero una vez que te alejas, comienza a caminar como si se estuviera deslizando y con la animación de disparando. Porque pasa esto?. Porque, a pesar de haber hecho la máquina de estado del enemigo correctamente, este solo regresa al estado de Idle/Walk una vez que la variable IsShooting está en false. En el Task RotateAndShoot ponemos esta variable en true una vez que el personaje está de frente al Player, pero en ningún punto del BT la ponemos en false.

Vamos a corregir este detalle y lo haremos en el Service CheckNearbyEnemy, en el punto en donde ya no se detecta que el Player está cerca vamos hacer que la variable isShooting vuelva a tomar su valor de false, para que el enemigo regrese de nuevo a su estado de caminando.

Señalado se encuentra la modificación al CheckNearbyEnemy para pasar la variable isShooting a false

Señalado se encuentra la modificación al CheckNearbyEnemy para pasar la variable isShooting a false

 

Compila, salva los cambios y corre el juego. Una vez que te acercas al enemigo este se detiene y comienza a disparar y si nos alejamos, continua caminando con la animación correcta.

Muy bien !!, ya tenemos a este personaje listo, pero aunque aparentemente nos dispara, por las animaciones que reproduce, en realidad en el momento del disparo no pasa absolutamente nada. Así que vamos ahora a implementar la lógica del disparo para poder determinar si le da al Player y poderle aplicar un daño a este.

Implementado lógica del disparo en el arma del enemigo

En el tutorial anterior, implementamos en C++ la lógica del disparo del arma que usa el Player. En este tutorial vamos a implementar la lógica del disparo del arma que usará el enemigo desde el Blueprint que creamos para esa arma. En realidad el modo de disparo es prácticamente idéntico y pudiéramos usar el ya implementado, pero lo vamos a hacer aquí de nuevo y desde blueprint, a modo de demostración.

En la clase Weapon, la clase base para todas las armas, tenemos el método virtual Fire, para poder implementarlo en cada arma según el tipo de disparo. Como tenemos el arma del enemigo en el blueprint M1GarandWeaponBlueprint, implementaremos aquí el método Fire de ella, pero antes tenemos que corregir algo que se nos escapó en el tutorial anterior.

En la declaración del método Fire de la clase Weapon, como atributos al macro UFUNCTION le pasamos BlueprintImplementableEvent. Este atributo permite que el método pueda ser implementado en el blueprint, pero tenemos que agregarle además el atributo BlueprintCallable, para que también se pueda llamar el método desde el Blueprint.

Abre el archivo Weapon.h y modifica los atributos del macro UFUNCTION del método Fire, para que te quede de la siguiente forma:

UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Weapon")
virtual void Fire();

Con esto ahora podemos implementar el método Fire en el Blueprint del arma del enemigo, y además llamarlo cuando se vaya a disparar el arma.

Abre el M1GarandWeaponBlueprint en el modo Graph y podrás agregar un nuevo evento, el Event Fire, y una vez agregado el nodo que representa este evento, podemos implementar toda la lógica que queramos. Vamos a implementar este evento de la siguiente forma:

VisualScript del evento  Fire del M1GarandWeaponBlueprint

VisualScript del evento Fire del M1GarandWeaponBlueprint

 

La lógica que seguimos aquí es prácticamente la misma que usamos en el tutorial pasado para el arma del Player. Primeramente lanzamos una línea imaginaria desde la posición del FireSocket, x unidades hacia delante. Recuerda que estas unidades están definidas en la variable ShotDistance, puedes darle el valor que prefieras a esa variable para definir el alcance del disparo.

Un detallito interesante a comentar, fíjate que para el rayo también restamos 30 unidades en el eje z, esto es un “parche“ por nuestro modo de juego scroll-side, las animaciones que tenemos y la posición en la que queda el rifle del enemigo cuando está disparando, que no queda alineado a la posición del Player, y debido a esto, si no hacemos este ajuste en la posición del rayo, este nunca colisionará con el personaje.

Otra posible solución y en realidad muy aplicada en este modo de juego, es que los personajes tengan una caja de colisión cuadrada algo ancha hacia los lados y estas colisiones controlarlas contra esta caja de colisión.

Bien, por último comprobamos si el rayo impactó con el Player y si es así le aplicamos un daño. Fíjate que, aunque no lo hacemos aquí, para las armas puede resultar interesante usar una variable para definir la cantidad de daño que esta ocasiona.

Por último, a diferencia del Fire del arma que usa el Player, en este caso no tenemos en cuenta las municiones. Un muy buen ejercicio que te puedes platear es implementar la lógica para que el enemigo tenga que recargar el arma si se queda sin municiones. Incluso, que pueda ir en búsqueda de municiones que estén en el escenario, recogerlas y después continuar con el ataque. En fin, lo puedes hacer todo lo complejo que quieras 😉

Muy bien, con esto ya tenemos el método de Fire de esta arma, ahora necesitamos llamar a este método en el momento en el que el enemigo hace el disparo. En el tutorial pasado implementamos un método llamado Attack en el HeroCharacter, donde comprobamos el arma que tiene equipada, e implementamos la lógica necesaria según el arma y finalmente llamamos al método Fire del arma. En realidad en este caso no es necesario un método así porque el enemigo ni tan siquiera tiene un inventario, tiene una sola arma y siempre la podrá disparar, pero vamos a aprovechar este punto para ver una cosilla nueva que tenemos en los blueprints. La posibilidad de crear funciones para encerrar determinada lógica y después poderla llamar simplemente como llamamos una función cualquiera.

Abre el AIEnemyCharacterBlueprint y fíjate que en el panel MyBlueprint, al lado del botón para crear una nueva variable, tenemos un botón para crear una función. Vamos a crear una nueva función de nombre Attack, en nuestro caso será muy simple, obtenemos la referencia del WeaponComponent y llamamos al método Fire, pero por ejemplo, si tu enemigo puede portar distintas armas la lógica de este proceso de atacar puede ser más compleja y por eso sería conveniente que la tengas en una función aparte, como mismo haríamos en C++.

Después de creada la función Attack, entra en su modo de edición e implementa lo siguiente:

Función Attack creada en el blueprint del enemigo. Esta función se ejecutará cuando el enemigo dispare su arma.

Función Attack creada en el blueprint del enemigo. Esta función se ejecutará cuando el enemigo dispare su arma.

 

Listo !! ya tenemos el método Fire del arma y el método Attack del enemigo, solo nos va quedando el evento que usaremos para llamar al método Attack.

Si abres la animación Fire_Shotgun_Ironsights en el Persona Editor y la analizas con detenimiento, verás que el momento exacto del disparo es un poquito después que inicia la animación. Pues bien, será en ese preciso momento donde ejecutaremos el método Attack que acabamos de crear y para esto usaremos los Notifies.

Crea un Notify casi al inicio de la animación Fire_Shotgun_Ironsights (donde se ve que es justamente el inicio del disparo) y dale de nombre FireNotify. Con esto haremos que el método que tiene toda la lógica del disparo del arma se llame en el momento justo.

Captura del Fire_Shotgun_Ironsights, después de crear el FireNotify

Captura del Fire_Shotgun_Ironsights, después de crear el FireNotify

 

Ahora abre el Blueprint Animation del enemigo, agrega el nodo del Notify que acabamos de crear y llama el método Attack, para que dispare el arma que tiene equipada cunado la animación pase por ese punto.

Trozo del AIEnemyAmimBlueprint con el algoritmo a ejecutar cuando se lanza el FireNotify

Trozo del AIEnemyAmimBlueprint con el algoritmo a ejecutar cuando se lanza el FireNotify

 

Compila, salva los cambios y corre el juego. Acércate al enemigo y verás que ya en el momento del disparo, se lanza el Trace para simular la trayectoria del proyectil, pero como notarás, el trace traspasa el Player. Esto pasa porque en la configuración de colisión del Mesh del Player, el Trace Response no lo tenemos configurado para que bloquee el trace.

Abre el HeroBlueprint en el modo de componentes, ve al árbol de componentes y selecciona el Mesh. Desplázate a la sección Colisiones y busca el combo Collision Presets y selecciona Custom. Luego marca como Block el parámetro Visibility de la sección Trace Responses.

Configuración del Trace Responses en el Mesh del Player para que el rayo que se lanza cuando el enemigo dispara su arma colisiones con este

Configuración del Trace Responses en el Mesh del Player para que el rayo que se lanza cuando el enemigo dispara su arma colisiones con este

 

Salva y lanza nuevamente el juego. Desplázate hacia el enemigo para que te dispare, verás como ahora el rayo si impacta en el personaje.

Captura del juego en ejecución en el momento en el que un disparo del enemigo (derecha) colisiona con el Mesh del personaje protagónico (izquierda)

Captura del juego en ejecución en el momento en el que un disparo del enemigo (derecha) colisiona con el Mesh del personaje protagónico (izquierda)

 

Implementado la lógica cuando el Player recibe daño

Ya tenemos implementado cuando el disparo del enemigo impacta con el Player y se le aplica un Damage, ahora vamos a implementar la lógica necesaria para restar salud al Player en cada disparo hasta que muera.

Primeramente necesitamos una variable en el Player que represente la salud de este. Abre el archivo HeroCharacter.h y agrégale el siguiente atributo:

/** Salud del Player, disminuye cuando recibe daño, al llegar a cero el personaje muere */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Health)
float Health;

Por defecto, la salud del Player será 100. Abre el archivo HeroCharacter.cpp y agrégale al final del constructor lo siguiente:

//La salud del personaje inicialmente será 100
Health = 100;

Ahora, como vimos en el tutorial “Cómo causar daño a un personaje“, necesitamos implementar el evento “Any Damage“ que se dispara automáticamente en el Actor cuando recibe un Damage. Como toda la lógica de nuestro héroe, la hemos estado implementado en C++, implementaremos este método en C++ también. Aunque es válido aclarar, que si lo prefieres, lo puedes implementar en el Blueprint del Player.

De momento lo que haremos será restar la salud del Player cada vez que reciba daño, y cuando llegue a cero imprimir en la pantalla, a modo de log, que el Player ha muerto.

Abre el archivo HeroCharacter.h y agrega la declaración del método ReceiveAnyDamage para implementarlo.

/**
* Es llamado automáticamente por el Engine cuando se le aplica daño a este Actor
* @param Damage. Daño que se le aplica al player
* @param DamageType. Clase con la información del daño aplicado.
* @param InstigatedBy. Controller que causa el daño
* @param DamageCauser. Actor que causa el daño
*/
virtual void ReceiveAnyDamage(float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, class AActor* DamageCauser) OVERRIDE;

Pasa ahora a HeroCharacter.cpp y vamos a implementar el método

/**
* Es llamado automáticamente por el Engine cuando se le aplica daño a este Actor
* @param Damage. Daño que se le aplica al player
* @param DamageType. Clase con la información del daño aplicado.
* @param InstigatedBy. Controller que causa el daño
* @param DamageCauser. Actor que causa el daño
*/
void AHeroCharacter::ReceiveAnyDamage(float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, class AActor* DamageCauser)
{
	//Si el player esta vivo ...
	if (Health > 0)
	{
		//... decremento la vida con el daño aplicado
		Health -= Damage;
		
		//Mostramos un log en pantalla
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, FString::Printf(TEXT("La salud del personaje es : %f"), Health));
	}

	//Si la salud del Player llega a cero, muere !!
	if (Health == 0)
	{
		//Mostramos un log en la pantalla
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "El player ha muerto");
	}
}

Compila y lanza el juego. Avanza hacia el enemigo para que te comience a disparar. Verás que cada vez que te dispara, se imprimen en pantalla las vidas restantes del Player y cuando llega a cero, se imprime el log que queremos.

Perfecto !!, ahora vamos a darle un poco de ”vida” a estos dos momentos, reproduciendo las animaciones en el Player cuando recibe los disparos y finalmente cuando muere.

Implementando la animación de Hit y Death del Player

Para la animación de muerte del Player usaremos la animación Death_1 que viene en el AnimStarterPack. Primero tenemos que hacerle el retarget para usarla en el esqueleto de nuestro personaje. Busca en el Content Browser, dentro del AnimStarterPack, la animación Death_1, dale clic derecho, Retarget Anim Assets/Duplicate Anim Assets and Retarget. Selecciona de aquí el esqueleto que usa nuestro HeroCharacter.

Abre el HeroCharacter.h y como mismo tenemos un atributo para cargar desde el editor el montage con las animaciones cuando el personaje tiene equipada el arma, vamos a crear otro atributo de tipo AnimationAsset para cargarle desde el editor la animación de muerte.

/* Animacion del personaje al morir */
UPROPERTY(EditDefaultsOnly, Category = "Animations")
UAnimationAsset *AnimationDeath;

Hecho esto, pasa a HeroCharacter.cpp y en el punto donde imprimimos el mensaje de muerte, vamos a cambiarlo para reproducir la animación de muerte:

//Reproducimos la animación de muerte
Mesh->PlayAnimation(AnimationDeath, false);

Compila y abre el editor. En el blueprint del personaje, asigna al atributo Animation Death, la animación Death_1 que preparamos.

Captura del HeroCharacterBlueprint donde le asignamos al atributo Animation Death el Asset de animación correspondiente

Captura del HeroCharacterBlueprint donde le asignamos al atributo Animation Death el Asset de animación correspondiente

 

Si en este punto corres el juego verás un errorcillo. Se reproduce la animación de muerte cuando la salud llega a cero, pero si en ese momento sigues tocando las teclas de moverte, el personaje sigue desplazándose por el escenario desde el piso, y esto evidentemente está terrible !!. Para arreglarlo, usaremos el método del Controller, UnPossess. Recuerda un poco la teoría que sigue el Framework de Unreal: El PlayerController “posee” al Pawn del personaje y mediante este es que se manejan las entradas del usuario. Con el método UnPossess hacemos que el Controller “desposea” al Pawn del personaje, y así quedan inutilizadas totalmente todas las entradas del jugador. Además, al hacer esto, el Service que tenemos en el enemigo que determina si estamos cerca del Player ya no nos retorna verdadero y con esto, una vez que el enemigo mate al Player, continuará patrullando la zona.

Regresa al HeroCharacter.cpp y después que reproducimos la animación de muerte agrega las siguientes líneas:

//Unposses del Controller
Controller->UnPossess();

//Destruye el component de collision básica del player
CapsuleComponent->DestroyComponent();

El DestroyComponent del Capsule Component lo llamamos para destruir la capsula de colisión del Player, para si se da el caso en el que el enemigo nos mata dentro de su zona de patrullaje, no colisione con esta capsula y puede pasar por arriba del “cadáver“ sin problemas.

Captura del juego en ejecución una vez que el enemigo nos mata, y nuestro personaje queda muerto, tendido en el suelo.

Captura del juego en ejecución una vez que el enemigo nos mata, y nuestro personaje queda muerto, tendido en el suelo.

 

Muy bien, solo nos va faltando un detallito, cada vez que el Player reciba un disparo, sería genial que reprodujera alguna animación no crees ? Pero, en este caso tenemos dos detalles importante a tener en cuenta.

Primero, esta animación de hit, tenemos que reproducirla mediante un Montage para poder mezclarlas con la animaciones de caminando/reposo, para que el personaje pueda caminar cuando reciba el disparo y estas dos animaciones se fusionen. Segundo, tenemos dos casos, cuando recibe el disparo teniendo el arma equipada y cuando lo recibe sin tener el arma equipada, por lo que tenemos que usar dos animaciones distintas.

Para este tutorial usaremos la misma animación, porque la verdad es que no tengo ninguna otra a mano y el AnimStarterPack no tiene ninguna animación para este caso :( . . . de todas formas creo que a modo de demostración es suficiente. En el AnimStarterPack localiza la animación Hit_React_1 y realiza todo el proceso de retarget de animación para usarla en el esqueleto de nuestro héroe.

Abre el UsingShotgunAnimMontage que preparamos en el tutorial anterior. Arrastra la animación que acabamos de hacerle el retarget para el final del montage, justo después del Reload. Crea una nueva sección de nombre Hit justo al comienzo de la animación de Hit y en el bloque Sections, agrega una que reproduzca el Hit y después caiga en el Idle en loop ya que es justamente lo que queremos. El Player recibirá el disparo, reaccionará a este disparo con un pequeño gesto y continuará en su reposo con su arma en la mano.

Captura de la sección Montage del UsingShotgunAnimMontage. Al final tenemos la última sección, de nombre Hit, con la animación Hit_React_1

Captura de la sección Montage del UsingShotgunAnimMontage. Al final tenemos la última sección, de nombre Hit, con la animación Hit_React_1

Captura de la sección Sections Preview del UsingShotgunAnimMontage. La última sección es la que acabamos de crear, armada con el Hit y a continuación el Idle en loop.

Captura de la sección Sections Preview del UsingShotgunAnimMontage. La última sección es la que acabamos de crear, armada con el Hit y a continuación el Idle en loop.

 

Ahora crea otro Montage para el caso en el que se reciba un disparo sin tener ningún arma equipada. Crea un nuevo montage con el nombre HitAnimMontage, ponle como nombre en el slot UpperBody, el mismo nombre de slot que hemos estado usando para los montages, y agrégale la misma animación Hit_React_1 o si tienes otra mano, para no repetir esta misma , usa la tuya 😉

Captura del HitAnimMontage.

Captura del HitAnimMontage.

Ya tenemos los dos montages configurados, ahora solo nos falta usarlos. Abre el HeroCharacter.h y agrega el atributo para desde el editor cargar el montage que usaremos cuando reciba el disparo sin ningún arma equipada.

/* AnimMontage para la animacion del personaje cuando le impacta un disparo del enemigo y este se encuentra sin el rifle equipado */
UPROPERTY(EditDefaultsOnly, Category = "Animations")
UAnimMontage* HitAnimMontage;

Ahora pasa al HeroCharacter.cpp y en el método ReceiveAnyDamage, luego de restar la salud del Player, agrega la lógica necesaria para reproducir estos montages según sea el caso. El método completo te quedará así:

void AHeroCharacter::ReceiveAnyDamage(float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, class AActor* DamageCauser)
{
	//Si el player esta vivo ...
	if (Health > 0)
	{
		//... decremento la vida con el daño aplicado
		Health -= Damage;		

		//Reproducimos la animación de "hit" correspondiente segun tenga o no equipada el arma
		if (InventorySelectedSlot == EInventorySlot::Gun)
		{
			PlayAnimMontage(UsingShotgunAnimMontage, 1.f, "Hit");
		}
		else
		{
			PlayAnimMontage(HitAnimMontage, 1.f);
		}
		
		//Mostramos un log en pantalla
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, FString::Printf(TEXT("La salud del personaje es : %f"), Health));

	}

	//Si la salud del Player llega a cero, muere !!
	if (Health == 0)
	{
		//Mostramos un log en la pantalla
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "El player ha muerto");

		//Reproducimos la animación de muerte
		Mesh->PlayAnimation(AnimationDeath, false);

		//Unposses del Controller
		Controller->UnPossess();

		//Destruye el component de collision básica del player
		CapsuleComponent->DestroyComponent();
	}
}

Compila, salva los cambios y por ultimo acércate al enemigo. Haz las pruebas con y sin el arma equipada y así veras al Player reproduciendo la animación correcta para cada estado

Ahora solo nos queda implementar algo muy parecido para el enemigo, un muy buen ejercicio sería que lo intentaras hacer por tu cuenta. De cualquier forma, aquí te dejo como sería.

Causando daño al enemigo

Si corres el juego en este punto y le disparan al enemigo, observarás que pasa lo mismo que nos pasaba con el disparo del enemigo al Player, la colisión con el rayo es ignorada por el Mesh del enemigo, ya sabes como solucionar esto. Selecciona al enemigo desde el nivel y en el panel de detalles muévete hasta la sección de colisiones y ponle block al parámetro visibility de trace response.

Abre el AIEnemyCharacterBlueprint y crea una nueva variable de nombre Health y asígnale como valor inicial 100. Esta será la salud del enemigo, como mismo hicimos con el Player desde C++.

Variable Health del AIEnemyCharacterBlueprint para representar la salud de este personaje

Variable Health del AIEnemyCharacterBlueprint para representar la salud de este personaje

 

Abre el archivo SPAS12Weapon.cpp (el arma que usa el Player) y agrega el siguiente código en el método Fire() justo después que le pedimos el nombre al actor impactado, básicamente lo mismo que hicimos en el Fire del arma del enemigo.

//Aplica un daño de 20 al Actor al que le dió el disparo.
UGameplayStatics::ApplyDamage(OutHit.GetActor(), 20, NULL, NULL, NULL);

El método ApplyDamage que nos brinda el UGameplayStatics es el mismo que hemos usado desde el blueprint. Recibe los mismos parámetros:

Actor al cual se le va a aplicar el daño.
Daño a aplicar.
Event Instigator se usa para definir el Controller que causa el daño.
Damage Causer se usa para definir el actor que causa el daño.
Damage Type Class nos permite definir una clase con propiedades específicas para extender la información del daño aplicado.

Una vez hecho esto, nos falta implementar el evento Any Damage en el enemigo como mismo hicimos para el Player. Salva todo y compila el código.

Cuando el enemigo reciba un disparo del Player, le restaremos salud, si llega a cero se reproduce la animación de muerte (Death_2) de lo contrario reproduciremos otra animación y además de esto usaremos una variable bool que nombraremos TakingDamage, para controlar que mientras esté recibiendo daño, no haga ninguna de las otras tareas que tiene configuradas en el Behavior Tree.

Primero vamos a crear esta variable. Abre el AIEnemyAnimBlueprint, crea una nueva variable de tipo bool y de nombre Taking Damage.

Ahora vamos a preparar las animaciones. La de muerte la reproduciremos directamente con el Play Animation, así que no usaremos ningún montage, pero para el caso de la animación cuando recibe un disparo pero aún no muerte si usaremos un montage.

Crear un nuevo montage, ponle como nombre HitAnimEnemyMontage y arrastra hacia él las animaciones Hit_React_4 e Idle_Rifle_Hip_Break1 en este mismo orden y dale como nombre de Slot HitSlot.

Ahora, fíjate, antes de reproducir este Montage, pondremos la variable TakingDamage en true, pero necesitamos que en cuanto termine esta animación esta variable pase a false y para eso usaremos un BranchPoint al final de la animación. Crea uno y ponle de nombre StopHit

HitAnimEnemyMontage con la creación del BranchPoint casi al final de la animación

HitAnimEnemyMontage con la creación del BranchPoint casi al final de la animación

 

Recuerda que para poder reproducir este montage en el enemigo, necesitamos tener el slot configurado en el Animation Blueprint. Abre AIEnemyAnimBlueprint y agrégale un nuevo Slot y ponle como nombre HitSlot para poder reproducir el Montage que acabamos de crear. Por último, conecta el StateMachine con este slot y luego este último al Final Animation Pose.

Captura del AIEnemyAnimBlueprint con el HitSlot para poder reproducir el Montage HitAnimEnemyMontage, usado cuando el personaje recibe un disparo pero aún no muere

Captura del AIEnemyAnimBlueprint con el HitSlot para poder reproducir el Montage HitAnimEnemyMontage, usado cuando el personaje recibe un disparo pero aún no muere

 

Bien, ya tenemos todos los recursos necesarios así que abre el AIEnemyCharacterBlueprint y vamos a implementar el evento Any Damage que básicamente hará lo siguiente: Primero le restamos la vida al enemigo. Luego, en el caso de que la vida llegue a 0 reproducimos la animación de Death_2 y eliminamos la capsula de colisión del enemigo. En caso contrario ponemos la variable TakingDamage en true y luego reproducimos el HitAnimEnemyMontage.

Any Damage del enemigo

Any Damage del enemigo

 

Fíjate que aquí, si es el caso en el que el personaje aún no ha muerto, antes de reproducir la animación, ponemos en true la variable Taking Damage y mediante el BranchPoint que creamos al final de esa animación la pondremos en false nuevamente, así que abre el AIEnemyAnimBlueprint implementa el evento MontageBranchingPoint_StopHit que simplemente lo que hará es poner esa variable en false nuevamente.

tuto8_imagen_30

Si corres el juego en este punto, notarás que ya se reproduce la animación, pero el enemigo como que se desliza cuando está recibiendo el disparo. Esto sucede porque el BT sigue el procedimiento normal y no “sabe“ que el enemigo está recibiendo disparos. Precisamente para esto fue que creamos esta especie de “bandera“ Taking Damage que ponemos en true cuando nos da el disparo y en false cuando terminamos de reproducir la animación. Vamos a usar esa variable ahora para mediante un Service en el BT para evitar que si el enemigo está recibiendo daño pase a hacer cualquiera cosa.

Agrega un nuevo key en el blackboard de nombre TakingDamage y de tipo booleano. Crea un nuevo Service de nombre TakingDamage y modifícalo para que te quede de la siguiente forma:

Service TakingDamage para actualizar el valor del Key TakingDamage del BlackBoard del enemigo con el valor de la variable TakingDamage que toma valor de true el tiempo en el que el enemigo se está recuperando del disparo.

Service TakingDamage para actualizar el valor del Key TakingDamage del BlackBoard del enemigo con el valor de la variable TakingDamage que toma valor de true el tiempo en el que el enemigo se está recuperando del disparo.

 

Con este Service listo, solo nos queda modificar el BT agregando al inicio del árbol este Service y a continuación el Decorator correspondiente para impedir que continúe la ejecución del árbol en el tiempo en el que el enemigo está “recuperándose“ de un disparo.

BehaviorTree final del enemigo

BehaviorTree final del enemigo

 

Listo !! Guarda, compila, lanza el juego y divierte un poco intentado matar al enemigo antes que él a ti 😉

Conclusión

Bueno, esto es todo por hoy, espero que te haya sido útil este tutorial. Un muy buen ejercicio que te puedo recomendar, es que intentes agregar más enemigos al nivel, tal vez en posiciones distintas, con distintas armas que causen más o menos daño, armas con lógicas de disparo distintas, en fin . . . todo lo que se te ocurra.

Otra cosa que te quería comentar. Como vez, la lógica de recibir daño y usar un arma, entre el enemigo y el Player es prácticamente idéntica. En proyectos reales, donde generalmente tendremos varios personajes que compartan lógicas muy parecidas o idénticas, no es nada recomendado implementar estas cosas de forma independiente por cada personaje. Lo ideal es crear una clase base, que encapsule la lógica común entre los personajes. Como las animaciones si serán distintas entre los personajes, estas las ponemos como propiedades de la clase, para que se pueda definir específicamente para cada personaje sin afectar la lógica (como hacemos aquí en el HeroCharacter). En este tutorial lo hemos hecho así sobre todo para demostrar las variantes C++ y Blueprint y que las puedas comparar e ir familiarizándote con ellas, pero no olvides aplicar este consejo en un proyecto real.

Ahora sí, esto es todo por hoy :). En próximos tutoriales veremos como implementar el HUD de nuestro juego, que es el mecanismo mediante el que el jugador tiene siempre en pantalla la salud del personaje, el arma equipada, la cantidad de municiones etc. También nos servirá para implementar una pequeña barra de salud sobre este enemigo. Además, veremos dos clases muy importantes para el núcleo del juego, el GameState y el GameMode. En fin, un montón de cosas interesantes vienen en próximos tutoriales, así que mantente al tanto, y mientras, bueno ya sabes . . . nos encantaría escuchar tus comentarios.

Implementando un inventario y usando un arma en UE4

En este tutorial crearemos un sistema de inventario para equipar armas e implementaremos la lógica para poder recoger, recargar, disparar el arma y detectar con qué colisiona el disparo. Esto nos permitirá ver varios conceptos y técnicas nuevas para nuestro juego. En este tutorial veremos:

  • Introducción a los Sockets, el mecanismo que nos brinda UE4 para anclar un elemento a otro en un punto determinado.
  • Uso del AnimMontage para las animaciones al usar el arma.
  • Uso del LineTrace para detectar colisión con una línea imaginaria. Con el uso de este método simularemos la trayectoria del disparo.
  • Implementaremos un sistema de inventario genérico para nuestro juego, donde el personaje podrá seleccionar, de las armas que tenga disponible, cuál usar.
  • Agregaremos los efectos de sonido al disparar y recargar el arma.
  • Veremos una introducción a los AnimNotifies para lanzar eventos en puntos exactos de una animación.
  • Y muchas cosas más, así que no te lo pierdas.

Preparando los recursos necesarios

Puedes descargarte de aquí los recursos que usaremos en este tutorial. Al descomprimir el .zip encontrarás varios efectos de sonidos para el arma y el FBX de una SPAS-12. Aprovecho para decirte que el modelo de la SPAS-12 lo descargué de: tf3dm.com. En ese sitio podrás encontrar montón de modelos 3D gratis, listos para importarlos en tus proyectos de pruebas y prototipos, así que si con el MarketPlace no estas conforme, ahí te dejo otro lugar donde encontrar recursos ;).

Además del modelo del arma y los efectos de sonidos, también está incluido en los recursos las clases Weapon, SPAS12Weapon, HeroCharacter como quedarán al terminar el tutorial, para una referencia más rápida.

Importa todos estos recursos al proyecto. Es bueno que mantengas los elementos organizados en el Content Browser, por ejemplo, puedes crear una carpeta de nombre Weapons, dentro de ella, las carpetas particulares para cada tipo de arma que tengas en tu juego. Dentro de cada una coloca todos los recursos que son de esta arma. Puedes también crear dentro de esta carpeta, otra carpeta llamada Sounds, para tener ahí los efectos de sonido. En fin, organízalo como mejor sea para ti, pero siempre es bueno tener el Content Browser bien organizado 😉

Después que importes los .wav de los efectos de sonidos, crea los Sound Cue correspondientes.

. . . Ya ?? . . . vale, comenzamos.

Introducción a los Sockets en Unreal Engine 4

Nuestro personaje tendrá la posibilidad de encontrar armas en el escenario, recogerlas y equiparlas. Cuando recogemos un arma, tendremos que agregar el modelo del arma en el guarda pistola del personaje, en este caso será en la parte de atrás del cinturón. De igual forma, cuando equipa el arma para usarla, la tendremos que anclar a la mano del personaje. Pues bien, para este tipo de cosas Unreal Engine nos brinda los Sockets

Desde el Persona Editor podemos crear un socket en una posición relativa a un hueso del esqueleto. Estos sockets, que básicamente son puntos invisibles, los podemos rotar o trasladar relativos a la posición del hueso en donde se ha creado y podemos anclar otros objetos en esta posición.

Por ejemplo, en nuestro caso lo que haremos será crear dos sockets, el primero lo crearemos relativo al hueso spine_01, para que quede justo donde nuestro personaje guardará la escopeta. El segundo lo crearemos relativo a la mano del personaje, para anclar el arma a este socket cuando la vaya a usar.

Creando los sockets necesarios en el esqueleto del personaje

Abre el esqueleto que usa nuestro personaje (HeroTPP) y en el panel Skeleton Tree (esquina superior izquierda) tenemos la estructura de huesos del esqueleto, aquí se muestra el árbol de huesos de este esqueleto. Selecciona el hueso spine_01, da clic derecho y selecciona Add Socket y dale de nombre HolsterSocket, este nombre lo usaremos desde programación para poderle decir a la escopeta en que socket se va a anclar cuando el personaje la recoja.

Captura del Persona Editor agregando el Socket relativo al hueso spine_01

Captura del Persona Editor agregando el Socket relativo al hueso spine_01

 

Ahora da clic derecho sobre el HolsterSocket desde el Skeleton Tree, selecciona Add Preview Asset y selecciona el StaticMesh de nuestra escopeta. Esto nos permite agregar a este socket a modo de pre-visualización el objeto que finalmente anclaremos aquí. De esta forma podemos ajustar la posición y rotación del HolsterSocket en base al punto de pivote del otro objeto.

Agregando al HolsterSocket el StaticMesh de la SPAS-12, a modo de pre-visualización.

Agregando al HolsterSocket el StaticMesh de la SPAS-12, a modo de pre-visualización.

 

En este punto vale aclarar una cosa. Como notarás, al anclar la escopeta aquí, el punto de anclaje está en la culata de la escopeta, que es el punto de pivote del modelo. El punto de pivote de un objeto en Unreal Engine 4 determina el punto sobre el que se hará cualquier transformación (traslación, rotación o escala). Este punto de pivote siempre está localizado en el origen (0,0,0) cuando se exporta el modelo desde el software de modelado 3D. Para el caso de las armas, es buena idea antes de exportar el modelo, garantizar que este punto de pivote esté sobre el gatillo del arma, de esta forma evitamos conflictos a la hora de colocar distintos modelos de armas en un mismo socket.

Puedes ver la posición del punto de pivote de cualquier StaticMesh, abriéndolo desde el Content Browser y marcando en el Toolbar del StaticMesh Editor, el botón Pivot Point.

Muy bien, ahora necesitamos mover y rotar el socket para que la SPAS-12 quede en la posición correcta, ya teniendo la pre-visualización del modelo, es muy fácil ajustar la posición y rotación correcta para el socket. Selecciona el HolsterSocket y con las herramientas de rotación y transformación ve rotando y moviendo el socket hasta que la SPAS-12 te quede en la posición correcta.

Captura del Persona Editor después de rotar y trasladar el HolsterSocket, dejando la SPAS-12 en la posición correcta

Captura del Persona Editor después de rotar y trasladar el HolsterSocket, dejando la SPAS-12 en la posición correcta

 

Un truco bastante útil para lograr la posición exacta es pre-visualizar la animación que tendrá que ver con este socket, en este caso es la animación de desenfundar la escopeta. Así puedes detener la animación en frames determinados y mover o rotar el socket teniendo como referencia la postura del personaje.

Muy bien, ahora crea un nuevo socket en el hueso hand_r (mano derecha) y dale de nombre HandSocket. Agrega la pre-visualización de la SPAS-12 a este socket, y de la misma forma que acabamos de hacer, traslada y rota el socket hasta que la escopeta quede en la posición correcta. Puedes cargar la animación de Idle_Rifle_Hip para que el preview del esqueleto se vea con esta animación. De esta forma te será más fácil posicionar el socket.

Captura del Persona Editor después de configurar el HandSocket

Captura del Persona Editor después de configurar el HandSocket

 

Perfecto !!, ya tenemos listo los dos sockets que necesitamos en nuestro personaje. Vamos ahora ha implementar la lógica de nuestra arma.

Implementando las clases Weapon y SPAS12Weapon

Vamos a implementar las clases para las armas. Tendremos una clase base llamada Weapon, esta clase tendrá la lógica común para cualquier tipo de arma y será una clase abstracta, o sea, no podemos tener instancias de ella, solamente servirá de clase base para las armas especificas. Además, tendremos la clase SPAS12Weapon que heredará de Weapon y será nuestra escopeta.

Crea una nueva clase que herede de Actor y nómbrala Weapon y crea otra clase que herede de Weapon y nómbrala SPAS12Weapon.

Abre el archivo Weapon.h y modifícalo para que te quede de la siguiente forma:

#pragma once

#include "GameFramework/Actor.h"
#include "Weapon.generated.h"

/**
 * Clase Base abstracta de todas las armas.
 */
UCLASS(abstract)
class UE4DEMO_API AWeapon : public AActor
{
    GENERATED_UCLASS_BODY()

protected:
    
    /**
     * USphereComponent es un componente en forma de esfera generalmente usado para detectar colisiones simples
     * Este será el Root del arma y con él detectaremos las colisiones entre el personaje y el arma
     */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
    TSubobjectPtr<USphereComponent> BaseCollisionComponent;
    
    /** StaticMesh del arma.  */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
    TSubobjectPtr<UStaticMeshComponent> WeaponMesh;

    /** Cantidad máxima de municiones */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
    int32 MaxAmmo;

    /** Alcance del disparo */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
    int32 ShotDistance;
    
    /** Efecto de sonido del disparo */
    UPROPERTY(EditAnywhere, Category="Sounds")
    USoundCue* ShotSoundEffect;
    
    /** Efecto de sonido cuando no hay munición */
    UPROPERTY(EditAnywhere, Category="Sounds")
    USoundCue* EmptySoundEffect;
    
public:

    /** Cantidad de municiones actuales */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
    int32 Ammo;
    
    /** True cuando el arma está en el escenario y false cuando es recogida por el Pawn */
    bool bIsEquipped;
    
    /**
     * Es llamado desde el Pawn cuando colisiona y recoge el arma
     * De momento solamente pone en true el flag bIsEquipped y muestra un log en la pantalla
     */
    void OnCollected();
    
    /**
     * Dispara el arma y obtiene el objeto que colisionó con el disparo
     * Método abstracto para sobreescribir en las clases hijas según las características especificas del disparo de cada arma
     */
    UFUNCTION(BlueprintImplementableEvent, Category="Weapon")
    virtual void Fire();
    
    /** Reinicia las municiones del arma */
    void Reload();
};

Aquí lo único nuevo que tenemos es el uso de UCLASS(abstract). Así es como le decimos al Engine que esta clase es abstracta. La clase Weapon está compuesta por los siguientes atributos:

BaseCollisionComponent: Componente que usaremos como ROOT de este actor y para detectar la colisión.

WeaponMesh: Mesh del arma y que cargaremos desde el Editor.

MaxAmmo: Máxima cantidad de municiones

Ammo: Cantidad de municiones disponibles, se decrementa con cada disparo.

ShotDistance: Alcance del disparo. Para la implementación del disparo, como veremos más adelante, lo que haremos es lanzar un rayo imaginario una x distancia hacia delante, el primer objeto que colisione con ese rayo será el que reciba el disparo. Para variar un poco el comportamiento entre las armas, el largo de ese rayo lo definiremos usando esta variable, esto nos permitirá definir un alcance especifico para cada arma.

ShotSoundEffect: Efecto de sonido del disparo de esta arma. Lo cargaremos desde el editor.

EmptySoundEffect: Efecto de sonido cuando se intenta disparar el arma estando sin municiones. También lo cargaremos desde el editor.

bIsEquipped: Este será un flag que usaremos para controlar que no se continúe detectando la colisión cuando el arma ya sea recogida por el Character. Como mismo hicimos con las monedas en el segundo tutorial.

Además de esto tenemos dos métodos

OnCollected: Este método será llamado desde el Character cuando recoja el arma. Le daremos una implementación general aquí. Solamente lo usaremos para poner en true el bIsEquipped y para imprimir un log en la pantalla. Si quisiéramos hacer algo en particular con cada arma en el momento en el que es recogida por el personaje, entonces tendrías que tener una implementación especifica de este método en las clases hijas.

Fire: En este método estará toda la lógica cuando se dispara el arma, decremento de las municiones, reproduce el efecto de sonido correspondiente y lanza el rayo para determinar con que a colisionado el disparo. Es un método virtual, que no tendrá implementación en esta clase, sino que lo implementaremos según el arma en especifico. Es válido aclarar que si en tu juego la lógica del disparo será la misma entre todas las armas, ya que puede ser el caso de que no tengas una gran diferencia del modo de disparo entre las armas, puedes dejar la implementación en esta clase base y que la hereden todas las armas.

Reload: Este método es llamado por el jugador cuando decide recargar el arma (tocando la tecla R) y simplemente reinicia la cantidad de municiones disponibles con el valor de MaxAmmo

Abre ahora el archivo Weapon.cpp y modifícalo para que te quede de la siguiente forma:

#include "UE4Demo.h"
#include "Weapon.h"


AWeapon::AWeapon(const class FPostConstructInitializeProperties& PCIP)
    : Super(PCIP)
{
    bIsEquipped = false;
    
    //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
    WeaponMesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("WeaponMesh"));
    
    //Settea el Profile de collision del este objeto en OverlapAll para que no bloquee al personaje
    WeaponMesh->BodyInstance.SetCollisionProfileName(FName(TEXT("OverlapAll")));
    
    //Agregamos el UStaticMeshComponent como hijo del root component
    WeaponMesh->AttachTo(RootComponent);
}

void AWeapon::OnCollected()
{
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Weapon Collected !!");
    
    bIsEquipped = true;
}

void AWeapon::Reload()
{
    Ammo = MaxAmmo;
}

En el constructor tenemos la inicialización del RootComponent y el MeshComponent. A estas alturas no debes tener problema con entender eso. Además setteamos el profile de colisión del arma en OverlapAll ya que usaremos el evento que se dispara al estar haciendo overlap con la pistola para poder recogerla del escenario.

También tenemos la implementación de los métodos OnCollected y Reload que son súper simples.

Ahora vamos a implementar la lógica de nuestra escopeta. Abre el archivo SPAS12Weapon.h y agrégale la declaración del override para el método Fire, te quedará así:

#pragma once

#include "Weapon.h"
#include "SPAS12Weapon.generated.h"

UCLASS()
class UE4DEMO_API ASPAS12Weapon : public AWeapon
{
    GENERATED_UCLASS_BODY()

    virtual void Fire() OVERRIDE;
};

Pasa ahora a SPAS12Weapon.cpp y modifica su contenido para que te quede de la siguiente forma:

#include "UE4Demo.h"
#include "SPAS12Weapon.h"


ASPAS12Weapon::ASPAS12Weapon(const class FPostConstructInitializeProperties& PCIP)
    : Super(PCIP)
{
    //Cantidad máxima de municiones para esta arma
    MaxAmmo = 4;
    
    //Cantidad de municiones para esta arma
    Ammo = 4;

    //Alcance máximo de la escopeta
    ShotDistance = 5000;
}

void ASPAS12Weapon::Fire()
{
    //Si la escopeta aún tiene municiones ...
    if(Ammo > 0)
    {
        //HitResult de la primera colisión del trace
        FHitResult OutHit;
        
        //Punto inicial para el Trace. La posición del FireSocket en el Mesh de la escopeta
        FVector TraceStart = WeaponMesh->GetSocketLocation("FireSocket");
        
        //Punto final para el Trace. Otro punto en linea recta a [ShotDistance] unidades de distancia
        FVector TraceEnd = (WeaponMesh->GetRightVector() * ShotDistance) + TraceStart;
        
        //Parámetros adicionales usados para el trece, en este los valores por defecto son suficiente.
        FCollisionQueryParams TraceParams;
        
        //Lanza un rayo desde TraceStart hasta TraceEnd, retorna true si se encontró alguna colisión y en OutHit el HitResult de la primera colisión
        bool bHit = GetWorld()->LineTraceSingle(OutHit, TraceStart, TraceEnd, ECC_Visibility, TraceParams);
        
        //Si el rayo colisionó con algo ...
        if (bHit)
        {
            //... Imprime en pantalla el nombre del Actor (a modo de prueba)
            GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, OutHit.Actor->GetName());
            
            //Dibuja una linea, a modo de Debug, desde la posicion inicial hasta el punto de impacto
            DrawDebugLine(GetWorld(), TraceStart, OutHit.ImpactPoint, FColor::Red, false, 5.f);
            
            //Dibuja un punto en la zona del impacto, a modo de Debug.
            DrawDebugPoint(GetWorld(), OutHit.ImpactPoint, 16.f, FColor::Red, false, 5.f);
        }
        else //el rayo no colisionó con nada ...
        {
            //Dibuja una linea, a modo de Debug, desde la posicion inicial hasta la final
            DrawDebugLine(GetWorld(), TraceStart, TraceEnd, FLinearColor::Red, false, 5.f);
        }
        
        //Reproduce efecto de sonido del disparo en la posición de la pistola
        if (ShotSoundEffect)
        {
            UGameplayStatics::PlaySoundAtLocation(this, ShotSoundEffect, GetActorLocation());
        }
        
        //Decrementa la cantidad de municiones disponibles
        Ammo--;
    }
    else //Si la pistola no tiene municiones ...
    {
        GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Sin municiones :(");
        
        //Reproduce efecto de sonido de la escopeta vacía
        if (EmptySoundEffect)
        {
            UGameplayStatics::PlaySoundAtLocation(this, EmptySoundEffect, GetActorLocation());
        }
    }
}

En el constructor solamente necesitamos definir por defecto la cantidad de municiones y el alcance del disparo, recuerda que de todas formas gracias al macro UPROPERTY, estos valores los podemos modificar desde el editor, en la sección Weapon, en el panel de propiedades de este objeto. Además, tenemos el método estrella de nuestra arma :), el Fire. Vamos paso a paso con lo que hacemos en este método:

Primero tenemos que comprobar que el arma tenga municiones, si es así, preparamos los parámetros que usaremos en el método GetWorld->LineTraceSingle. Este método nos permite lanzar un rayo invisible desde un punto a otro, nos retorna si se detectó alguna colisión con él y por referencia en la variable OutHit viene toda la información del primer Hit, encapsulada en la estructura FHitResult. Puedes ver la implementación de esta estructura en los fuentes del Engine para que tengas una idea de toda la información que tenemos del impacto. De momento solo nos interesa el Actor con el que se ha colisionado.

El método recibe como parámetros la variable OutHit (de salida), además los vectores de inicio y fin del rayo. Aquí vamos a detenernos para ver de que forma armamos este rayo. Si lo que queremos es un rayo que simule la trayectoria del proyectil, el primer punto que necesitamos estaría en el cañón de la escopeta y el otro punto lo necesitamos alineado a este punto, en la dirección del cañón, una x distancia por delante. Acordamos que esta distancia la íbamos a definir mediante la variable ShotDistance, para tener la posibilidad de variar el alcance de cada arma.

Muy bien, pero tenemos un problema. Como obtenemos el punto exacto en la salida del cañón de la escopeta ? . . . se te ocurre algo ? . . . pues claro !!, mediante un socket. Los Sockets no solo los podemos crear en un esqueleto, también los podemos crear en un StaticMesh. Además, este socket no solo nos servirá para saber este punto, también nos puede servir, por ejemplo, para en el momento del disparo anclar y emitir desde aquí un sistema de partículas para simular el efecto de humo y fuego del arma al dispararse. Aún no hemos visto los sistemas de partículas en estos tutoriales, pero un muy buen ejercicio investigativo es que busques por tu cuenta un poco de información sobre estos e intentes acoplar aquí ese efecto en el momento del disparo. Después me encantaría que nos dejaras tu solución en los comentarios y la agregamos al tutorial como complemento 😉

Muy bien, si te fijas en el código, para obtener el punto inicial del rayo lo hacemos a partir de la línea WeaponMesh->GetSocketLocation(“FireSocket”). El método GetSocketLocation nos retorna la posición del socket con el nombre pasado como parámetro. Por supuesto, este socket tiene que existir en el Mesh. Vamos entonces al editor para configurar este socket en el StaticMesh de nuestra escopeta.

Abre el StaticMesh de la escopeta. En el Toolbar del Static Mesh Editor, hay un botón para activar o desactivar la pre-visualización de los sockets, asegúrate de tenerlo activado. Desde el menú principal del Static Mesh Editor selecciona Window/Socket Manager. Esto te agregará en la esquina inferior derecha del editor, debajo del panel Details, el panel Socket Manager. Desde este panel podrás agregar y configurar sockets a este StaticMesh prácticamente de la misma forma que hicimos con el esqueleto del personaje.

Selecciona el botón Create Socket, dale de nombre al nuevo socket FireSocket y muévelo para posicionarlo en la punta del cañón.

Captura del Static Mesh Editor con el modelo de la escopeta y con el FireSocket en la posición correcta.

Captura del Static Mesh Editor con el modelo de la escopeta y con el FireSocket en la posición correcta.

 

Listo !!, guarda y cierra el editor. Regresamos al código del método Fire para seguir analizándolo. Ya tenemos el punto de inicio del rayo, ahora nos falta el punto final. El tercer parámetro que recibe el método es el punto final del rayo y este lo obtenemos gracias a unos métodos súper útiles que tenemos para los Mesh, son los método GetForwardVector, GetRightVector, GetUpVector. Estos métodos nos retornan un vector unitario en cada una de las direcciones del Mesh. En este caso necesitamos el método GetRightVector que nos da un vector unitario en la misma dirección del cañón. Con este vector y un poco de matemática muy simple, conseguimos nuestro punto final. Para obtener el punto final necesitamos multiplicar este vector por el valor que queremos para la distancia del disparo, en este caso guardado en la variable ShotDistance, y el resultado lo sumamos al vector inicial. Esta operación nos dará el punto final del rayo con el que vamos a simular la trayectoria de la bala.

El cuarto parámetro del LineTraceSingle es el “canal“ que usará este rayo para detectar las colisiones. Recuerdas que en el tutorial anterior vimos una introducción a las colisiones en Unreal Engine 4 y que hablamos de los Collision Responses de cada objeto y que se dividían en dos grupos: los Trace Responses para las colisiones con rayos, como es el caso, y los Object Response para las colisiones con otros objetos. Recuerdas ? … pues bien, este parámetro permite definir que tipo de Trace Response usará el rayo, en este caso indicamos Visibility. Si le das un vistazo a cualquier objeto en la sección Collision verás que para los Trace Response existen dos opciones Visibility y Camera. Una cosa muy importante, los objetos que puedan recibir un disparo, o sea, que este rayo pueda colisionar con ellos, tienen que tener marcado en Block el Trace Response: Visibility, ya que este es el canal que estamos usando para este rayo. De lo contrario, aunque visualmente notemos que el rayo colisiona, no se detectará la colisión.

El último parámetro son opciones adicionales que podemos usar para el rayo, de momento con sus valores por defecto tenemos suficiente. Puedes revisar la implementación de esta estructura para que veas los atributos que tiene.

Al llamar al método LineTraceSingle, en la variable bHit tendremos si se colisionó con algo o no, y en la variable OutHit tendremos la información del primer hit. Con esto, lo único que nos queda es comprobar si bHit es true, si es así, imprimimos un mensaje en la pantalla con el Nombre del objeto con el que colisionó el rayo y además con la ayuda de los métodos DrawDebugLine dibujamos, a modo de debug, una línea para poder ver la trayectoria del disparo y validar que todo esté funcionando como tiene que ser.

En próximos tutoriales vamos a determinar en este punto, si la colisión con el disparo a sido con un enemigo, y en ese caso le aplicaremos un daño, aunque los mecanismos para aplicar daño ya los hemos visto en tutoriales anteriores, y sería súper bueno que te adelantes e implementes esto por tu cuenta.

En caso que bHit esté en false, es que no se detectó colisión con ningún objeto, esto puede pasar porque no hay ningún objeto en el trayecto del disparo, al menos a una distancia menor que ShotDistance. En ese caso también imprimimos en pantalla un texto temporal y dibujamos una línea a modo de debug.

Después de esto, si ShotSoundEffect es válido, reproducimos el efecto de sonido en la posición de la pistola. Recuerda que ShotSoundEffect y EmptySoundEffect los tenemos como propiedades de la clase y la idea es que desde el editor se pueda cargar, para cada una de estas propiedades, el efecto de sonido correspondiente.

Por último, decrementamos la cantidad de municiones.

En caso de que la pistola se quede sin municiones, simplemente preguntamos si EmptySoundEffect es válido, y si lo es, reproducimos el efecto de sonido.

Listo !!, ya tenemos la implementación completa de la escopeta que podrá usar nuestro personaje, pero aún no tenemos nada “visible“ para probar, ya que necesitamos antes, implementar el mecanismo para que el personaje pueda recoger y equipar la escopeta.

Tómate unos minutos y seguimos con eso 😉

Implementando un sistema de inventario

Nuestro personaje tendrá distintas formas de defenderse. Podrá usar las manos y defenderse a base de puñetazos, como ya vimos en los dos tutoriales anteriores, pero además, podrá equipar distintas armas y usarla contra sus enemigos.

En los juegos que tienen estas características, es clásico contar con un sistema de inventario, donde el jugador puede agregar el arma al espacio del inventario correspondiente a ese tipo de objeto y seleccionar del inventario el objeto que quiere equipar para usar.

Para no extender el tutorial, solamente implementaremos la lógica para usar un arma, en este caso una escopeta, pero al terminarlo verás lo fácil que resultará agregar otras armas.

Primero, vamos a explicar un poco el principio que usaremos. Básicamente un inventario lo podemos ver como un cajón imaginario con distintos compartimentos. Cada uno de estos compartimentos puede estar vacío o almacenar un elemento específico. En nuestro caso, sería un cajón con dos compartimentos. El primero puede tener una escopeta, y en el caso que la tenga, la podemos seleccionar para defendernos con ella, el segundo sigue el mismo principio solo que en vez de escopeta o armas pesadas, estará destinado para pistolas.

Ejemplo gráfico del inventario. En este caso el inventario estaría lleno, pero si tuviéramos, por ejemplo, solo la escopeta, el segundo elemento del cajón estaría vacío.

Ejemplo gráfico del inventario. En este caso el inventario estaría lleno, pero si tuviéramos, por ejemplo, solo la escopeta, el segundo elemento del cajón estaría vacío.

 

El elemento ideal que tenemos para implementar este tipo de cosas a nivel de programación son los arreglos. Un arreglo, viéndolo abstractamente, es un cajón en la memoria con distintos compartimentos. Podemos obtener el elemento que se encuentra en un compartimento a partir del índice (su posición en el arreglo, siendo 0 la primera posición).

El único detalle a tener en cuenta es que en un arreglo todos los elementos tienen que ser del mismo tipo, pero usando la herencia y el polimorfismo (conceptos básicos de la programación orientada a objetos) podemos definir que nuestro arreglo será de elementos de una clase base, digamos Weapon y podemos tener instancias de objetos que heredan de Weapon, como sería Pistol para el caso de la pistola, por ejemplo, y Shotgun para el caso de la escopeta.

Muy bien, ya basta de teoría y vamos a la practica.

Abre el archivo HeroCharacter.h y agrega al inicio del fichero, debajo de los includes, lo siguiente:

#define INVENTORY_GUN_INDEX         0
#define INVENTORY_PISTOL_INDEX      1

UENUM()
namespace EInventorySlot
{
    enum Type
    {
        Gun,
        Pistol,
        None
    };
}

Los dos #defines son simplemente constantes para usar a la hora de acceder a cada posición del arreglo. A continuación creamos un enum que usaremos para identificar el tipo seleccionado en el inventario. En nuestro caso tendremos tres variantes Pistola, Escopeta o Ninguno. Este último caso sería si el usuario no ha seleccionado ningún elemento del inventario, ya sea porque no tiene o porque no ha querido, en este caso se estaría defendiendo a puñetazos.

Muévete, hasta el final de la clase y agrega los siguientes atributos:

/** Evento cuando este AActor se superpone con otro AActor */
virtual void ReceiveActorBeginOverlap(class AActor* OtherActor) OVERRIDE;

/* Inventario del player. En el primer index se tendrá un arma tipo escopeta y en el segundo un arma tipo pistola */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Inventory")
TArray<class AWeapon*> Inventory;

/* Compatimento del inventario actualmente seleccionado por el player */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Inventory")
TEnumAsByte<EInventorySlot::Type> InventorySelectedSlot;

El atributo Inventory es una arreglo de Weapons . En este guardaremos, en el índice correspondiente, la referencia de cada una de las armas recogidas. En este caso usaremos el índice 0 para las armas de tipo escopeta y el índice 1 para las pistolas.

El atributo InventorySelectedSlot lo usaremos para saber en cada momento cual es el elemento del inventario actualmente en uso.

Bien, pasa ahora a HeroCharacter.cpp y agrega al final del constructor las siguientes líneas

//Reserva el espacio para los items en el inventario. En este momento cada uno de los espacios está en NULL
Inventory.AddZeroed(2);

//Inicialmente no hay ningún elemento del inventario seleccionado
InventorySelectedSlot = EInventorySlot::None;

Con la primera línea lo que hacemos es reservar el espacio en memoria para nuestro inventario. El método AddZeroed de TArray nos permite agregar la cantidad de elementos que indiquemos como parámetro al arreglo, pero en NULL, o sea, vacíos, no hay nada ahí. Es como tener el cajón con todos sus compartimentos vacíos.

Además de esto, inicializamos la variable InventorySelectedSlot en None, o sea, no tendremos nada equipado, en este caso la única forma que tendríamos de defendernos son las manos.

Ahora vamos a implementar la lógica para poder recoger el arma del escenario. De momento lo haremos muy simple, las armas estarán dispersas por el nivel y cuando le pasemos por arriba, automáticamente esta se agregará al inventario si no tenemos ninguna otra arma de ese tipo. Para esto usaremos el método ReceiveActorBeginOverlap del personaje. Este método es el que se dispara cuando se detecta un Overlap entre este Actor y otro. Recuerda que para que se dispare este método tiene que estar en true el atributo Generate Overlap Events.

Muy bien, sabiendo esto, agrega la implementación del método ReceiveActorBeginOverlap en HeroCharacter.cpp:

/** Evento cuando este AActor se superpone con otro AActor */
void AHeroCharacter::ReceiveActorBeginOverlap(class AActor* OtherActor)
{
    Super::ReceiveActorBeginOverlap(OtherActor);
    
    //Chequea si el objeto con el que se está colisionando es un Weapon
    if(OtherActor->IsA(AWeapon::StaticClass()))
    {
        //Casteamos OtherActor a AWeapon dado que el parámetro de ReceiveActorBeginOverlap es de tipo AActor
        AWeapon *OtherActorAsWeapon = Cast<AWeapon>(OtherActor);
        
        //Si el arma con la que estamos colisionando NO ha sido equipada, o sea, que está en el escenario ...
        if(OtherActorAsWeapon->bIsEquipped == false)
        {
            //Chequea si el arma con la que se está colisionando es un ASPAS12Weapon
            if(OtherActor->IsA(ASPAS12Weapon::StaticClass()))
            {
                //Si el espacio para las armas de tipo escopeta del inventario está libre...
                if(Inventory[INVENTORY_GUN_INDEX] == nullptr)
                {
                    //Colocamos en el primer index del arreglo Inventory una referencia a este objeto
                    Inventory[INVENTORY_GUN_INDEX] = OtherActorAsWeapon;
                    
                    //Adjunta la pistola al Socket del Mesh del personaje, donde guarda las armas de tipo escopeta.
                    OtherActorAsWeapon->AttachRootComponentTo(Mesh, "HolsterSocket", EAttachLocation::SnapToTarget);
                    
                    //Ejecuta la lógica que tenga el arma cuando es tomada por el personaje
                    //En este caso simplemente se pone en false bIsEquipped y se muestra un log en la pantalla
                    OtherActorAsWeapon->OnCollected();
                }
                else //... si ya tienes en el compartimento del inventario para las escopetas, una escopeta ...
                {
                    // ... mostramos un log en la pantalla.
                    //Otra variante puede ser, incrementar las municiones de esa arma
                    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Ya tienes una escopeta equipada !!");
                }
            }
            
            //@TODO:
            //Aquí puedes agregar el chequeo de otro tipo de arma, como una pistola por ejemplo,
            //y agregarlo el Inventory usando el index INVENTORY_PISTOL_INDEX
            
        }
    }
}

Ve con detenimiento por los comentarios, línea a línea, para que puedas entender en cada paso lo que se hace. A modo de resumen, lo que hacemos es determinar si estamos colisionando con un arma, si es así, miramos que tipo de arma es, para agregarla al índice indicado en el arreglo Inventory. Además, la anclamos al HolsterSocket del personaje.

Muy bien !!, vamos a probar esto. Compila y abre el editor. Desde el panel Mode selecciona All Classes y agrega al escenario una SPAS12Weapon, recuerda colocarla alineada al personaje para que este pueda colisionar con ella.

Ahora, desde el panel Details en la sección Static Mesh selecciona el Mesh de la SPAS12. En este mismo panel, busca la sección Sounds y verás las dos propiedades Shot Sound Effect y Empty Sound Effect. Carga en cada una el Sound Cue correspondiente que creamos al inicio del tutorial, después de importar los .wav.

Captura del editor con la SPAS12Weapon agregada al escenario después de seleccionarle el Mesh

Captura del editor con la SPAS12Weapon agregada al escenario después de seleccionarle el Mesh

 

Como notarás el SphereComponent de la escopeta tiene su centro en la culata de la escopeta. Este es otro problemita que se crea por tener el punto de pivote de este StaticMesh en esa posición, por eso hay que tener en cuenta el punto de pivote al exportar el modelo desde el software de modelado 3D. De momento, para “disimular“ un poco el problema, podemos rotar la escopeta para que salga apuntando hacia arriba.

Muy bien !!, crea una copia de la escopeta y colócala en otra posición, esto para rectificar que tal como programamos, cuando el personaje tenga en el inventario una escopeta, si le pasa por arriba a otra, no la podrá recoger. Por supuesto, esta lógica la podemos cambiar si queremos, por ejemplo, se pudiera comprobar que si tienes ya en el inventario esa arma lo que se obtengan sean municiones. En fin, ya esto depende a lo que quieras en tu juego, de momento lo haremos así para mantenerlo simple.

Listo!! guarda, corre el juego y camina hacia la escopeta. Cuando le pases por encima a la primera, se detectará el evento Overlap y se agregará la pistola al Mesh del personaje en el HolsterSocket (en la parte de atrás del cinturón). Ahora pasa por arriba de la segunda escopeta, verás que esta se mantendrá en el escenario y se mostrará en la pantalla el log: “Ya tienes una escopeta equipada !! ”, tal como queríamos.

Captura del juego una vez que se recoge la escopeta del escenario y esta queda anclada en el HolsterSocket del personaje.

Captura del juego una vez que se recoge la escopeta del escenario y esta queda anclada en el HolsterSocket del personaje.

 

Muy bien !!, ya tenemos el mecanismo necesario para recoger las armas, vamos ahora con la segunda parte, cómo usarlas.

Preparando el AnimMontage para las animaciones cuando se está usando la escopeta

Para el uso de la escopeta en general se necesitan varias animaciones. Necesitamos una animación para equipar la escopeta, otra para dispararla, otra para recargarla cuando se le agoten las municiones y una última para guardarla. Vamos a usar para esto algunas de las animaciones que vienen en el AnimStarterKit que bajamos del MarketPlace en el tutorial pasado, recuerdas ?. En ese paquete de animaciones tenemos todas las que necesitamos, no son perfectas, pero suficiente para el desarrollo del tutorial.

Pero, antes de poderlas usar tenemos un pequeñito problema, y es que estas animaciones vienen con su propio esqueleto, así que tenemos que apoyarnos de una opción que nos da Unreal Engine 4 que es genial. Y es la posibilidad de hacer un retarget de una animación de un esqueleto a otro. Para esto el esqueleto tiene que ser el mismo, como es nuestro caso.

Busca en el Content Browser las siguientes animaciones: Equip_Rifle_Standing, Idle_Rifle_Hip, Fire_Shotgun_Hip, Reload_Shotgun_Hip y ha cada uno dale clic derecho Retarget Anim Assetes/Duplicate Anim Assets and Retarget. Esto te abrirá una ventana para seleccionar el nuevo esqueleto. Selecciona de aquí el esqueleto que usa nuestro HeroCharacter. Repite esto para cada animación y al terminar ya tendremos las animaciones necesarias.

Ventana de selección del esqueleto para hacer el Retarget de la animación

Ventana de selección del esqueleto para hacer el Retarget de la animación

 

Ahora vamos a preparar un AnimationMontage con estas animaciones. En el Content Browser, dentro de la carpeta donde tengas las animaciones del personaje, crea un nuevo AnimMontage y nómbralo UsingShotgunAnimMontage. Dale como nombre de Slot: UpperBody. Todas estas animaciones queremos que se fusionen con las animaciones del State Machina para, por ejemplo, en lo que caminamos poder disparar.

Arrastra las siguientes animaciones en este mismo orden a la sección Montage del AnimMontage: Equip_Rifle_Standing, Idle_Rifle_Hip, Fire_Shotgun_Hip, de nuevo Equip_Rifle_Standing (vamos a usar esta misma animación para la acción de guardar el arma, pero en realidad deberíamos tener una animación específica para esta acción), por último Reload_Shorgun_Hip. Debes tener el Montage así:

Sección Montage del UsingShotgunAnimMontage después de agregar las animaciones

Sección Montage del UsingShotgunAnimMontage después de agregar las animaciones

 

Ahora crea los siguientes Montage Sections: EquipShotgun para el inicio, sustituye el Default por este. IdleShotgun para el inicio de la animación Idle_Rifle_Hip. FireShotgun para el inicio de la animación Fire_Shotgun_Hip, HolsterShotgun para el inicio de Equip_Rifle_Standing (recuerda que la usaremos también para guardar la escopeta) y por último ReloadShotgun al inicio de Reload_Shotgun_Hip. Te quedará de la siguiente forma:

Sección Montage del UsingShotgunAnimMontage después de crear los Montage Sections

Sección Montage del UsingShotgunAnimMontage después de crear los Montage Sections

 

Perfecto, vamos a preparar ahora las secciones para este AnimMontage. En el bloque Sections da clic en el botón Clear. Ahora, como mismo hicimos en el tutorial de los puñetazos, crea las siguientes secciones:

 

Bloque Sections del UsingShotgunAnimMontage después de crear configurar los Montage Sections

Bloque Sections del UsingShotgunAnimMontage después de crear configurar los Montage Sections

 

La idea de esto es obtener el siguiente comportamiento. Para la primera sección, queremos que el personaje desenfunde la escopeta y se quede en el estado Idle_Rifle_Hip en loop. Para la segunda sección queremos que el personaje dispare la escopeta y también se quede en Idle, para la cuarta sección, es simplemente guardar la escopeta, así que después de guardar ya no se usaría más este Montage porque se regresaría a las animaciones del StateMachine. Por último, para la última sección, queremos que el personaje recargue la escopeta y regresa a su estado idle en loop también.

Perfecto !!, ya casi estamos listo para probar, pero nos queda un detalle. Si te fijas en la animación Equip_Rifle_Standing, esta animación comienza y es más o menos por la mitad de la animación cuando el personaje llega a tener la mano sobre la escopeta. O sea que es exactamente en este momento donde tendríamos que desanclar la escopeta del HolsterSocket del personaje y anclarla al HandSocket. Bien, seguro que ya sabes como resolver esto verdad ?? . . . exacto! Con los Branch Point del Montage.

Esto lo necesitamos tanto para la animación de equipar el arma, como para enfundarla así que crea en la posición correcta, apoyándote con la pre-visualización, estos dos Branch Points y nombralos EquipShotgun y HolsterShotgun. Te quedarán así:

BranchsPoints creados en el UsingShotgunAnimMontage en los puntos exactos donde se enfunda y desenfunda el arma.

BranchsPoints creados en el UsingShotgunAnimMontage en los puntos exactos donde se enfunda y desenfunda el arma.

 

Muy bien, ya tenemos casi listo nuestro AnimMontage. Digo casi listo, porque aún nos queda incorporarle un detalle para la reproducción de los efectos de sonido, pero de momento vamos a probar sin ellos. Guarda estos cambios y cierra.

Antes de pasar para el código tenemos que refactorizar dos cosillas en el AnimGraph del HeroAnimBlueprint y también en el Blueprint del personaje, además tenemos que configurar los nuevos inputs que tendremos en nuestro juego. Primero, abre el Blueprint del personaje y elimina el InputAction Punch donde ponemos en true/false la variable Is Punching del personaje. Esta lógica la vamos a cambiar para que se ajuste a nuestro nuevo mecanismo de defensa que ahora cuenta con un inventario con armas, además de los puños.

Por otro lado, abre el AnimGraph del HeroAnimBlueprint y cambia el nombre del Slot a UpperBody (después lo cambiaremos en el AnimMontage de los puñetazos, no te preocupes). Además de esto, selecciona el nodo Layered blend per bone y en el panel Details pon en true la opción Mesh Space Rotation Blend. Esto es necesario para evitar que al hacer el Blend entre las animaciones que usaremos para el uso de la escopeta y las de locomoción, quede rotada la columna del personaje, esto nos permite asegurarnos que la dirección a la que apuntará el cuerpo será siempre la correcta. Puedes probar después poner esta propiedad en false para que veas cual es el efecto más claramente.

tuto7_imagen_13

Muy bien, ahora vamos a ajustar los nuevos controles de nuestro juego. Abre el Project Settings/Input y configura los Bindings de tipo Action de la siguiente forma:

Nueva configuración de los Action Mappings

Nueva configuración de los Action Mappings

 

Como notarás tenemos cuatro Action Mappings nuevos. ReloadWeapon (R) lo usaremos para recargar el arma. Attack (Clic izquierdo del Mouse) lo usaremos para atacar. ActivateInventorySlot1 y ActivateInventorySlot2 (teclas 1 y 2 del teclado respectivamente). Estas las usaremos para activar cada elemento del inventario, con la tecla 1 activaríamos el elemento en el primer compartimento, si tenemos alguno, y con la tecla 2 el elemento del segundo compartimento.

Listo !! guarda y cierra el editor que nos regresamos al código.

Abre el archivo HeroCharacter.h y agrega al final de la declaración los siguientes atributos y métodos

/*
 * Flag para saber si el player está atacando.
 * Se hace true cuando se presiona el click derecho del mouse y false cuando se suelta
 */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Pawn")
bool IsAttacking;

/*
 * Es llamado cuando se presiona la tecla 1.
 * Enfunda/desenfunda el arma que esté en el primer compartimento del inventario (si hay alguna)
 */
void ActivateInventorySlot1();

/*
 * Es llamado cuando se presiona la tecla 2.
 * Enfunda/desenfunda el arma que esté en el segundo compartimento del inventario (si hay alguna)
 */
void ActivateInventorySlot2();

/* AnimMontage para las animaciones del personaje cuando está usando la escopeta */
UPROPERTY(EditDefaultsOnly, Category="Animations")
UAnimMontage* UsingShotgunAnimMontage;

//@TODO: Aquí puedes incluir los AnimMontage para el uso de otras armas

public:

/** 
 * Se llama en el BranchPoint de la animación en el momento de equipar el arma
 * Ancla el arma seleccionada a la mano del personaje
 */
UFUNCTION(BlueprintCallable, Category="Animations")
void OnEquipActiveWeapon();

/**
 * Se llama en el BranchPoint de la animación en el momento de guardar el arma
 * Ancla el arma al cinturón del personaje
 */
UFUNCTION(BlueprintCallable, Category="Animations")
void OnHolsterWeapon();

/** Inicia el ataque con el arma equipada */
void Attack();

/** Recarga el arma equipada */
void ReloadWeapon();

IsAttacking: Lo usaremos como un flag para saber cuando el personaje esté atacando. Lo pondremos en true mientras esté presionado el clic izquierdo del Mouse y en false cuando se suelte.

ActivateInventorySlot1: Este método lo llamaremos cuando el jugador presione la tecla 1 y activará el primer elemento del inventario.

ActivateInventorySlot2: Este método lo llamaremos cuando el jugador presione la tecla 2 y activará el segundo elemento del inventario. En este tutorial no le daremos ninguna implementación, así que queda por tu cuenta agregar otro tipo de arma para el segundo espacio en el inventario 😉

UsingShotgunAnimMontage: Es la instancia del AnimMontage que usará el personaje para las animaciones del uso de la escopeta. Tiene que ser configurado desde el editor.

OnEquipActiveWeapon: Este método será llamado por el BranchPoint que creamos en la animación de desenfundar la escopeta y en este preciso momento se desanclará el arma de HolsterSocket del personaje y se anclará en HandSocket.

OnHolsterWeapon: Es lo contrario del método anterior. Será llamado por el BranchPoint que creamos en la animación de guardar la escopeta y en ese preciso momento se desanclará el arma del HandSocket del personaje y se anclará en el HolsterSocket.

Attack: Este método lo llamaremos cuando el jugador presione/suelte el clic izquierdo del ratón y se encargará de iniciar el ataque con el arma equipada.

ReloadWeapon: Por último, este método lo usaremos para recargar el arma equipada. Será llamado cuando el jugador presione la tecla R.

Muy bien, pasa ahora a HeroCharacter.cpp. Inicializa en false el atributo IsAttacking al final del constructor y agrega las siguientes líneas al final del método SetupPlayerInputComponent:

InputComponent->BindAction("ActivateInventorySlot1", IE_Released, this, &AHeroCharacter::ActivateInventorySlot1);
InputComponent->BindAction("ActivateInventorySlot2", IE_Released, this, &AHeroCharacter::ActivateInventorySlot2);

InputComponent->BindAction("Attack", IE_Pressed, this, &AHeroCharacter::Attack);
InputComponent->BindAction("Attack", IE_Released, this, &AHeroCharacter::Attack);

InputComponent->BindAction("ReloadWeapon", IE_Released, this, &AHeroCharacter::ReloadWeapon);

Agrega ahora al final de la clase la implementación de los métodos ActivateInventorySlot1 y ActivateInventorySlot2

/*
 * Es llamado cuando se presiona la tecla 1.
 * Enfunda/desenfunda el arma que esté en el primer compartimento del inventario (si hay alguna)
 */
void AHeroCharacter::ActivateInventorySlot1()
{
    //Si el Slot actualmente seleccionado es distinto al de la escopeta ...
    if(InventorySelectedSlot != EInventorySlot::Gun)
    {
        //Si el slot para la escopeta está vacío ...
        if(Inventory[INVENTORY_GUN_INDEX] == nullptr)
        {
            //Mostramos un log en la pantalla
            GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "No tienes ninguna escopeta para equipar");
        }
        else //... si el slot de la escopeta NO está vacío ...
        {
            //Mostramos un log en la pantalla
            GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Equipando la escopeta");
         
            //Reproduce AnimMontage en la primera sección, que es la animación de equipar la escopeta
            if(UsingShotgunAnimMontage)
            {
                PlayAnimMontage(UsingShotgunAnimMontage);
            }
            
            //Actualiza el valor de InventorySelectedSlot para saber en otro momento que actualmente tenemos seleccionada la escopeta
            InventorySelectedSlot = EInventorySlot::Gun;
        }
    }
    else if(InventorySelectedSlot == EInventorySlot::Gun) //Si tenemos equipada la escopeta ...
    {
        //Lanza animación para guardarla (Section HolsterShotgun del UsingShotgunAnimMontage)
        if(UsingShotgunAnimMontage)
        {
            PlayAnimMontage(UsingShotgunAnimMontage, 1.f, "HolsterShotgun");
        }
    }
}

void AHeroCharacter::ActivateInventorySlot2()
{
    //@TODO: Implementar la lógica para activar/desactivar el arma del segundo compartimento del inventario
}

Como siempre, ve detenidamente línea a línea y apoyándote en los comentarios para que entiendas a fondo lo que se hace. En general al presionar la tecla 1 comprobamos si NO tenemos equipada la escopeta, si no la tenemos equipada rectificamos que en el primer index del arreglo Inventory, que es el espacio destinado para las armas de tipo escopeta, exista algún elemento. Si está vacío no podemos equipar nada por lo que de momento mostramos un log en la pantalla , solo para que el usuario lo sepa. Si NO está vacío, entonces reproducimos la animación para equipar la escopeta y actualizamos el valor de InventorySelectedSlot a Gun. Del resto se encargará el método OnEquipActiveWeapon en cuanto se dispare el BranchPoint correspondiente. Por último, en caso que ya tengamos una escopeta equipada, pues lo que hacemos es reproducir la animación de guardarla.

El método ActivateInventorySlot2 te lo dejo para que lo implementes tú 😉 teniendo como referencia el ActivateInventorySlot1 te será muy fácil hacerlo, además sería un muy buen ejercicio.

Vamos ahora a implementar los métodos OnEquipActiveWeapon y OnHolsterWeapon. Agrega al final del HeroCharacter.cpp las siguientes implementaciones:

/**
 * Se llama en el BranchPoint de la animación en el momento de equipar el arma
 * Ancla el arma seleccionada a la mano del personaje
 */
void AHeroCharacter::OnEquipActiveWeapon()
{
    //Rectificamos que InventorySelectedSlot sea igual a Gun para poder saber que index de Inventory usar
    if(InventorySelectedSlot == EInventorySlot::Gun)
    {
        //Obtenemos la referencia al arma en el INVENTORY_GUN_INDEX del arreglo
        AWeapon *Weapon = Inventory[INVENTORY_GUN_INDEX];
        
        if(Weapon)
        {
            //Desancla el arma del HolsterSocket, para anclarla en la mano
            Weapon->DetachRootComponentFromParent(true);
            
            //Ancla la pistola al Socket en la mano
            Weapon->AttachRootComponentTo(Mesh, "HandSocket", EAttachLocation::SnapToTarget);
        }
    }
}

/**
 * Se llama en el BranchPoint de la animación en el momento de guardar el arma
 * Ancla el arma al cinturón del personaje
 */
void AHeroCharacter::OnHolsterWeapon()
{
    //Rectificamos que InventorySelectedSlot sea igual a Gun para poder saber que index de Inventory usar
    if(InventorySelectedSlot == EInventorySlot::Gun)
    {
        //Obtenemos la referencia al arma en el INVENTORY_GUN_INDEX del arreglo
        AWeapon *Weapon = Inventory[INVENTORY_GUN_INDEX];
        
        if(Weapon)
        {
            //Desancla el arma del HandSocket, para anclarla en la mano
            Weapon->DetachRootComponentFromParent(true);
            
            //Ancla la pistola al HolsterSocket
            Weapon->AttachRootComponentTo(Mesh, "HolsterSocket", EAttachLocation::SnapToTarget);
            
            //Reinicia el valor de InventorySelectedSlot a None, para saber que no tenemos ningún arma equipada
            InventorySelectedSlot = EInventorySlot::None;
        }
    }
}

Estos dos métodos son casi uno la inversa de otro. El primero es llamado una vez que el usuario selecciona la tecla 1 para activar el arma que tiene en el primer compartimento del inventario. En ese momento, inicia la animación para desenfundar el arma, y cuando la animación está en el punto donde el personaje tiene la mano ya sobre la escopeta, gracias al BranchPoint que creamos, se llama el método OnEquipActiveWeapon. Este método obtiene la referencia del arma desde el inventario, la desancla del HolsterSocket y la ancla al HandSocket del personaje, para que este la pueda tomar en la mano. Por otro lado, el método OnHolsterWeapon es lo contrario.

Muy bien, ya tenemos implementada la lógica para equipar/guardar el arma. Ahora vamos a implementar la lógica para dispararla. Agrega al final del HeroCharacter.cpp la implementación del método Attack

/** Inicia el ataque con el arma equipada */
void AHeroCharacter::Attack()
{
    //Si IsAttacking es false, se está presionando el clic izquierdo del mouse ...
    if(IsAttacking == false)
    {
        IsAttacking = true;
        
        //Hacemos un switch por los posibles valores que puede tomar el InventorySelectedSlot
        //para saber que arma está equipada e iniciar la lógica de correspondiente
        switch (InventorySelectedSlot)
        {
            case EInventorySlot::Gun: //Si está equipada la escopeta
            {
                //Obtenemos la referencia de la escopeta
                AWeapon *Weapon = Inventory[INVENTORY_GUN_INDEX];
                
                //Si aún tiene municiones ...
                if(Weapon->Ammo > 0)
                {
                    //Reproducimos la animación del disparo con la escopeta
                    if(UsingShotgunAnimMontage)
                    {
                        PlayAnimMontage(UsingShotgunAnimMontage, 1.f, "FireShotgun");
                    }
                }
                
                //Llamamos al método Fire del arma
                Weapon->Fire();
                
                break;
            }
            case EInventorySlot::Pistol:
            {
                //@TODO: Iniciar ataque con pistola
                
                break;
            }
            case EInventorySlot::None:
            {
                //@TODO: Iniciar ataque con golpes
                
                break;
            }
            default: break;
        }
    }
    else //Esta atacando, detiene la acción de atacar (se soltó el clic izquierdo del mouse)
    {
        IsAttacking = false;
    }
}

Este método es llamado cuando en el juego presionamos/soltamos el clic izquierdo del mouse. Al ser presionado, hacemos un switch para determinar que valor tiene la variable InventorySelectedSlot, para saber que arma es la que tenemos equipada, y con la que vamos a atacar. Si es EInventorySlot::Gun quiere decir que tenemos equipada la escopeta. En este caso obtenemos la referencia a la escopeta desde el inventario. Si la escopeta tiene municiones, reproducimos la animación del disparo de la escopeta y por último llamamos al método Fire del arma, que es el encargado de toda la lógica del disparo, como ya vimos.

Casi terminamos, pero si recuerdas cuando implementamos el método Fire de la escopeta, en cada disparo se decrementa la cantidad de municiones de esta. Si las municiones llegan a cero nuestro personaje no podrá disparar más. Para solucionar esto agregamos el método ReloadWeapon. Este método lo podemos llamar al presionar la tecla R y se encargará de comprobar cual es el arma equipada, reproducir la animación correspondiente y llamar al método Reload del arma, que tiene la lógica correspondiente para recargarla. En nuestro caso simplemente reiniciamos la cantidad de municiones. Pues bien, agrega la implementación del método ReloadWeapon al final de HeroCharacter.cpp

/** Recarga el arma equipada */
void AHeroCharacter::ReloadWeapon()
{
    //Si está activa la escopeta
    if(InventorySelectedSlot == EInventorySlot::Gun)
    {
        //Obtenemos la referencia
        AWeapon *Weapon = Inventory[INVENTORY_GUN_INDEX];
        
        if(Weapon)
        {
            //Reproducimos la animación de recargando el arma
            if(UsingShotgunAnimMontage)
            {
                PlayAnimMontage(UsingShotgunAnimMontage, 1.f, "ReloadShotgun");
            }
            
            //LLamamos al método Reload del arma
            Weapon->Reload();
        }
        
        return;
    }
    
    if(InventorySelectedSlot == EInventorySlot::Pistol)
    {
        //@TODO: Aquí puedes agregar la lógica para recargar la pistola
    }
}

Listo !! compila y abre el editor. Antes de darle Play, abre el Blueprint del personaje en el Modo Defaults y busca la propiedad Using Shotgun Anim Montage y selecciona el UsingShotgunAnimMontage que creamos hace un rato, compila y guarda los cambios.

tuto7_imagen_15.1

Por último, en el UsingShotgunAnimMontage creamos dos BranchPoints. Estos BranchPoints los usaremos para llamar a los métodos OnEquipActiveWeapon y OnHolsterWeapon en el momento exacto, como marcamos estos métodos como BlueprintCallable los podemos llamar desde el Blueprint sin problemas. Abre el Animation Blueprint del personaje y agrega lo siguiente:

Trozo del HeroAnimBlueprint donde se agrega la llamada a los método OnEquipActiveWeapon y OnHolsterWeapon a partir de los eventos generados por los BranchPoints creados en el UsingShotgunAnimMontage

Trozo del HeroAnimBlueprint donde se agrega la llamada a los método OnEquipActiveWeapon y OnHolsterWeapon a partir de los eventos generados por los BranchPoints creados en el UsingShotgunAnimMontage

 

Ahora sí !! listo para la acción !?? . . . compila, guarda y ejecuta el juego.

tuto7_imagen_17

Camina hacia la escopeta y cuando la tengas equipada toca la tecla 1 para activarla. Después que la tengas lista, da clic con el mouse para que comiences a disparar. Como configuramos la escopeta para que tenga un máximo de 4 balas, después de dar el cuarto disparo no podrás disparar más, porque te quedaste sin municiones, pero esto no es problema :) … toca la tecla R para que recargues el arma.

Agrega algún objeto al nivel al que le puedas disparar, asegúrate de configurarle su Collision Traces Response en Block para los Trace de tipo Visibility. Equipa la escopeta y dispárale. Verás que en la pantalla se muestra el log con el nombre del objeto, además se dibuja un punto rojo en el punto del impacto, que fue exactamente lo que implementamos. En próximos tutoriales haremos cosas más interesantes ;).

Por último, vuelve a tocar la tecla 1 para que guardes la escopeta.

Bueno, hasta ahora va genial todo, eh ?! . . . pero aún tenemos un detallito pendiente por agregar. Si revisas en los efectos de sonido que tenemos, aún nos quedan por usar dos efectos el shotgload.wav que es el efecto de sonido al recargar la escopeta y shotgr1b.wav que es el efecto al preparar la escopeta para próximo disparo. Pues bien, vamos a usar estos efectos en cada momento y para esto usaremos los Anim Notifiy.

Introducción a los Anim Notifies en Unreal Engine 4

Los Animation Notifications o simplemente AnimNotifies nos brindan una forma de definir eventos en puntos específicos de una animación. Son comúnmente usados para agregar efectos de sonido en la animación, por ejemplo, el efecto del pasos en el momento exacto donde en la animación queda el pie en el suelo. O para emitir un sistema partículas.

Vamos a usarlos en este caso para agregar el efecto de sonido al preparar la escopeta y al colocar las balas al recargarla.

Abre el UsingShotgunAnimMontage y muévete hasta la sección Notifies. Usa el timeline para pararte en el punto de la animación donde el personaje coloca la bala en la escopeta. Esta acción la hace cuatro veces, o sea, coloca cuatro balas, por lo que tendremos que agregar cuatro Notifies. En la sección Notifies da clic derecho justo en el punto exacto y selecciona Add Notify … fíjate que se despliegan varias opciones. Tenemos, PlayParticleEffect y PlaySound, estos dos son notifies pre-creados. Además podemos seleccionar New Notify para crear un Notify específico.

Una vez creado un Notify podemos recibir este evento desde el Animation Blueprint del personaje, como mismo hemos hecho con los Branch Point. En este caso no es necesario irnos por esta vía gracias al notify pre-construido PlaySound.

Entonces, en el punto exacto donde se colocan las balas da clic derecho/Add Notify/PlaySound y en el panel de detalles de ese notify, en la propiedad Sound selecciona del Content Browser el Sound Cue que creaste a partir del efecto shotgload.wav.

tuto7_imagen_18

Repite el proceso para los 4 momentos de la animación donde el usuario agrega una bala. Por último, muévete en el timeline hasta el punto en la animación del disparo donde el usuario prepara la escopeta para el próximo disparo y agrega otro PlaySoundNotify con el efecto shotgr1b.wav.

Listo !!, guarda y dale Play. Dispara la escopeta y podrás escuchar detrás de cada disparo, justo en el momento exacto, sincronizado con la animación, el efecto shotgr1b.wav. Ahora toca la tecla R para recargar y podrás escuchar también el efecto shotgload.wav justo en el momento exacto. . . súper verdad !! :)

Refactorizando el Animation Blueprint para los puñetazos.

Antes de terminar, tenemos que refactorizar el Animation Blueprint del personaje para poder dar puñetazos cuando no se tenga equipada ningún arma. Primero, abre el PunchingAnimMontage y cambia el nombre de slot a UpperBody. Ahora, abre el Animation Blueprint del personaje y modifícalo para que te quede de la siguiente forma:

Animation Blueprint del personaje. El bloque marcado en rojo es lo que se cambió.

Animation Blueprint del personaje. El bloque marcado en rojo es lo que se cambió.

 

Anteriormente aquí obteníamos directamente el valor de la variable IsPunching del personaje, esta variable se hacía true cuando se detectaba el evento Press el input Punch y false con el evento Release. Esto lo cambiamos para ajustarlo al nuevo mecanismo, ahora tenemos que comprobar si la variable IsAttacking está en true y el InventorySelectedSlot está en None, si estas dos condiciones se cumplen es que el personaje no tiene equipada ningún arma y está atacando, así que no queda otra que los puños 😉

Listo !! guarda, y dale play al juego. Sin recoger ningún arma, da clic y verás como el personaje comienza a lanzar puñetazos, puedes recoger el arma del escenario y dar clic y seguirá dando puñetazos. Ahora toca la tecla 1 para equiparla, cuando des clic lo que hará será disparar el arma. Toca de nuevo la tecla 1 para guardarla y vuelve a dar clic, volverá a lanzar puñetazos. Genial, no !?

Pues esto es todo por hoy. En próximos tutoriales estaremos agregando enemigos que nos dispararan al vernos, implementaremos el HUD del juego con el Unreal Motion Graphics y muchas otras cosas. Mientras tanto, me encantaría escuchar tus comentarios y recuerda que puedes seguirme en Twitter (@nan2cc), para que estés al tanto de los próximos tutoriales.

Making a 3D side-scroller game in UE4

Introduction

Let’s start summing up what we did in the previous tutorial. We started our series with a general introduction to Unreal Engine 4 through a project which explores some of the basic concepts of the engine. We saw the framework’s class hierarchy, the visual scripting, the character animation settings, and we defined a fixed camera as a temporary solution in our game, among other things. If you haven’t already read the previous tutorial, I strongly suggest you do before continuing with this one.

In this tutorial, we will create the base to our 3D side-scroller game’s style. Currently my team and I are working on an automatic Runner, 2D side-scroller for IOS and Android. You can follow us on our Facebook page to keep up with our work in progress and to know when it will release. If you like this kind of game, I can assure you that you will love ours ;).

As I already said, in this tutorial we will configure our game camera to achieve a side-scroller view. We will also add some coins to our scene and use simple collision mechanism that will allow the character to collect the coins. We will “teach” our character to run and jump :). We will also see some variable and function macros for the integration between the C++ code and the Editor… and so much more. Are you ready?!… Well let’s get started!!

Setting the level from the editor

Let’s start by making a small change to the current level through the editor to match the style of game that we want. The base to a side-scroller game is to have the camera parallel to the main character and to have the camera a certain distance away from the character on the y axis. The character moves only in two directions – upward and downward when he jumps, and left/right when he walks.

Open in the Editor the UE4Demo project that we used in the last tutorial and delete the visible objects that we won’t use (the chairs, the table, the statue, etc.), leaving only the floor object. After that, modify this object using the transformation and escalate tools situated in the upper-right corner of the viewport. Then create copies of the object, modify them indistinctly and spread them through the level. Make sure to leave the ‘Actor Play Start’ on one of the platforms, to avoid that the character free fall into abyss. This is how I did it (use your imagination to achieve a better result than mine :).

Modified level in the Editor to match our game style

Modified level in the Editor to match our game style

 

As you will notice, when the game runs, it shows a red alert which says: LIGHTING NEEDS TO BE REBUILT. This alert has to do with the fact that we change the level geometry and the engine needs to rebuild the light so the illumination and shadows fit the new geometry. You can rebuild the lights selecting the Toolbar/Build/Build Lighting Only option. Run again and you will notice that everything goes back to normal.

For now we have a very basic level but enough to implement and test all the things that we have planned to do.

Configuring the camera for a side-scroller game.

At this point, it’s important to clarify that UE4 has a default template to build this style of game. In fact, we will use practically the same elements that this template uses but the idea of this tutorial is to do it from scratch to understand the basic concepts of the game style.

First we will change the game camera. In the last tutorial, we configured a simple fixed camera that served the purpose of introducing us both into the visual scripting (Blueprint Editor) and the C++ code. We will no longer use this camera so go ahead and delete the current implementation of the camera both in code and in the Blueprint Editor. If you still have the code implementation, comment out the lines inside the Begin Play method. In the Blueprint you may delete all the nodes or, if you want to keep the references, delete the connection leaving the BeginPlay node. By deleting this connection, we break the camera algorithm because the execution doesn’t continue when the BeginPlay event is executed but we still hold the rest of the connections and references.

Let’s configure a new camera’s style, this time through code in our Character’s class. Always take into account that you can also do it through the Editor. Remember that we have a Blueprint class that inherits our HeroCharacter class. A good way to practice will be for you to adventure by yourself once we do it by code to do the same in the Editor by modifying the HeroCharacterBlueprint.

Open the HeroCharacter class and add the next declarations after the GENERATED_UCLASS_BODY() macro:


/** Spring arm to fix the camera to the Character to match the side-scroller style */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
TSubobjectPtr<USpringArmComponent> SpringArm;
    
/** Game camera, is attached to the arm’s socket to achieve the side-scroller’s camera style */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
TSubobjectPtr<UCameraComponent> SideViewCamera;

We add two class variables to our HeroCharacter, but I’m sure that the thing that caught your attention was the UPROPERTY macro included for each variable. This way we define whether or not we want to use these attributes in the Editor and what options we have to work with them. As we saw in the previous tutorial, one of the coolest things about UE4 is the seamless integration between the code and the editor and this macro is one of the powerful utilities to achieve it. Next, let’s look at some of this macro’s parameters:

VisibleAnywhere: Defines if this property will be visible in the Editor’s property panel.

BlueprintReadOnly: Defines if this property could be read from VisualScript in the Blueprint, but it can’t be modified.

Category: Allow to specify a category name under the properties will be listed in the Editor.

You can find a detailed reference of all of the configurable parameters to the UPROPERTY macro in the Runtime/CoreUObject/Public/UObject/ObjectBase.h inside the UP namespace. If you click over a category while pressing the cmd key in MACOS, or click one while holding ctrl in Windows, you will be taken to the specific reference for that macro.
After we implemented the constructor where we initialized these variables, go to the Editor to check the result of defining the attributes with this macro.

These two class attributes just created in HeroCharacter are of a data type we haven’t used before. The first one is USpringArmComponent that we named SpringArm. Notice that we used the parametrized TSubobjectPtr class to define it. This way we can use the CreateDefaultSubobject method of the PCIP object received in the constructor to create an instance of any data type, which we will in a second. The USpringArmComponent allows us to fix a component to its parent in a fixed distance. We will use this component to fix the game’s camera with a certain distance from the character.

We will also create the class attribute SideViewCamera of UCameraComponent data type. The class name is quite descriptive. This attribute is our game camera :).

Next we will initialize this variable in the class constructor. Open the HeroCharacter.cpp and add the following code block:


//Initializing the USpringArmComponent attribute
SpringArm = PCIP.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("CameraBoom"));
   
//Adding the springArm to the Character's RootComponent (the collision capsule)
SpringArm->AttachTo(RootComponent);
   
//bAbsoluteRotation allows us to define if this support will rotate with the player or will stay fixed.
//In our case we don't want it to rotate with the character.
SpringArm->bAbsoluteRotation = true;
   
//The distance between the arm and its target. This value defines the distance between the character and the camera.
//Try out different values to see the outcome.
SpringArm->TargetArmLength = 500.f;
   
//Socket Offset.
//Socket is an anchor point to other components.
//For instance, in the character case we can define the socket in the character's hand
//this way we can add another component(for example a gun).
//But in our case we will add a camera to our SpringArm
SpringArm->SocketOffset = FVector(0.f,0.f,75.f);
   
//The relative rotation of the arm regard to its parent.
//We want the camera rotated 180 degrees in the Y axis in order to be situated parallel to the character, at the same level.
//This way we achieve the classic side-scroller camera's style.
SpringArm->RelativeRotation = FRotator(0.f, 180.f, 0.f);
   
// Creating the UCameraComponent instance.
SideViewCamera = PCIP.CreateDefaultSubobject<UCameraComponent>(this, TEXT("SideViewCamera"));
   
//The AttachTo method allow us to add an object to another in a given socket. It receives two parameters,
//the first one, the object where we will be anchored (the springArm) and the second the socket's name where we will be anchored.
//USpringArmComponent's SocketName returns the name of the components socket.
SideViewCamera->AttachTo(SpringArm, USpringArmComponent::SocketName);

Pay attention to each line comments so you can understand what each stands for. Overall, we create and configure the USpringArmComponent’s object and then we add it to the Character. Next, we create and configure the UCameraComponent’s object (the game camera) and add it to the USpringArmComponent to fix it to a given distance from the Character. Notice that we create the instances with the received reference in the constructor class using PCIP.CreateDefaultSubobject.

Ready, build and play the game. Now you have the game view in the side-scroller style :). Try to move in the level just created to see how the camera follows your every moment, always at a fixed distance.

New game view configuration to match the side-scroller camera's style

New game view configuration to match the side-scroller camera’s style

 

Don’t close the editor. Let’s focus in HeroCharacterBlueprint. In the last tutorial, we created this blueprint in the Character folder. Open it and activate the Components mode (upper-right corner). Notice that now in the Character’s Components panel we have a new object: the SpringArm and it contains a child the SideViewCamera. Check out all the properties defined by code. You can also check all the default properties values, change the values, and check out the different outcomes.

To test the parameters effect of the UPROPERTY macro in practice, go back to the code and delete the VisibleAnywhere parameter set in the property and build again. When you open the HeroCharacterBlueprint in the editor, in spite of seeing the components, we can’t see the properties values in the details panel when we select them.

Modifying the game controls for a side-scroller’s game.

So far we have set the camera’s style but I’m sure you noticed that the game controls don’t fit our game’s style, given that the character can move both in X and Y axis which is very unusual in a side-scroller game. Therefore we’ll change the game controls a bit to adjust them to the side-scroller style. We will also add two new actions and controllers to our character: run and jump.

I assume that after the last tutorial you have a couple of ideas of how to add these actions. In the Editor, select Edit/Project Settings/Input and leave only the MoveRight entry. Now let’s add another input type the ActionBinding. These entries, unlike the AxisBinding entries, are used to execute specific actions e.g. to jump or open a door. Create a new entry of this data type and name it Jump. Select the space bar as the key control. If you look closely you will notice that we can also define that the action gets executed when two keys are pressed simultaneously. In this case, will only use the space bar to jump.

New configuration of the game's controls. Adding jump control.

New configuration of the game’s controls. Adding jump control.

 

Now let’s code. First of all, we no longer need the MoveForward method of the HeroCharacter class. Delete the method statement in the .h and the implementation in the .cpp. Also, in the SetupPlayerInputComponent method, delete the MoveForward BindAxis. Lastly, we have to modify the MoveRight implementation given that at the moment when the entry gets called we rotate the character position, now we want that the character to move forward and backward respectively. Modify the MoveForward as the code below:


/**
*  Gets called when the MoveForward entry is detected (When the user press the A or D keys).
*  @param Value Value is equal to 1 when the D is pressed and -1 when A is.
*/
void AHeroCharacter::MoveRight(float Value)
{
if ( (Controller != NULL) && (Value != 0.0f) )
{
        // Adds a new movement to the right or left according to the Value value.
        AddMovementInput(FVector(0.f,-1.f,0.f), Value);
}
}

Now this method is much simpler and I hope it’s easier to understand what it does. Anyway, I will explain step by step what we just did. We apply a movement vector that affects only the y axis of the vector’s value. So when the user presses the D key, the character will move forward (to the right) and when the A key is pressed, the character will move backward, meaning to the left side.

Now we have to implement the method that will get called to execute the jump action when the user presses the space bar. It’s very simple. Just add the next line to the SetupPlayerInputComponent method:

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

Notice one thing, the method that gets called is the ACharacter::Jump. Jump is a method implemented in our base class, not in our own class HeroCharacter. This method already has all that we need to make the character jump. It’s really amazing how much help the framework provides us with :).

Go ahead build and play the game. Press the A and D keys to see how the character moves correctly on the scene, and how when you press the W and S keys, nothing happens. Hit the space bar and you will see how our character now also jumps :) … but something is still missing. Yes the character is jumping but visually is playing the walk animation. Next thing we need to do is add the jump animation.

Setting the jump animations for the character

Since the last tutorial, we have the main character resources that supposedly our design team have us, among them we have three FBX animations to jump: the Jump_End.FBX, Jump_Loop.FBX, Jump_Start.FBX and the Run.FBX file. Import them in the Animation folder. You can inspect them in the Persona Editor.

I’m sure that the fact that we have three different animations for the jump caught your attention, and you ask yourself, “Why??”. I will give you the answer right away but first let me share a personal experience with you involving this aspect: When we start the development of our current project, you can take a look in here :)). We struggle with this problem.
In our game, the character jumps at different heights. For instance, it can do a small jump to avoid a box or a long jump from a cliff and stays longer in the air. The problem is that in the long jump case we play the jump animation when the action is started with a duration and might end while the character is still in the air falling in the last frame of the jump animation until it touches the ground where the transition will continue normally. That’s the reason why in many cases, it’s necessary to divide the jump animation in three pieces, we have the start jumping action then we have a loop animation that is played while the character is falling and finally the animation when the character touches the ground. This is how we achieve the whole jump animation cycle independently of the height.

Let’s configure it in the character animation blueprint. As we saw in the last tutorial, we use a state machine for the animation system that allows us to separate the different states that our character supports and set an animation for each state. Currently the state machine of our character is very simple, containing only the Idle state and the Walking state. Let’s add the jumping states.

Inside the HeroAnimBlueprint/AnimGraph, enter the state machine, drag the cursor from the Idle/Walk node to create a new node, and name it JumpStart. Repeat the procedure twice using the last created node to create the JumpLoop node and finally the JumpEnd. At the end, connect the JumpEnd state to Idle/Walk to close the cycle. It should look like this:

New Character state machine, with the jumping states added

New Character state machine, with the jumping states added

 

We have just defined three new states to our character and the order in which they would be reached. In other words, the character could be in rest or running and can change to the JumpStart state (jump starts). From the JumpStart state, he can reach the JumpLoop (loop cycle) and then the JumpEnd state. Lastly, he could go to the Idle/Walk state. Notice the directional arrows between the states. These are the connections that define the origin and destination states. If you jump you will notice the four states and the blending between them.

Each state has an icon over the transition arrow. This icon represents the condition that triggers the transition between the states. We have to define this condition using the visual scripting in the Blueprint Editor. The first case is when the character is in the Idle/Walk state and starts the jump action. The condition to pass to this state will be BOOL variable that will store if the character is in the air or not. When the character changes the state such that it is in the air, we will change to the JumpStart state.

Double click in the transition icon to enter in its edition mode. By default we have a Result node with the “Can Enter Transition” description. This node expects a bool parameter that the state machine will use to decide if the state transition takes place. Create and add a new variable like we did in the last tutorial but this time use bool as data type. Name it IsInAir or any name that you prefer but it must be something meaningful. Add it to the blueprint in GET mode and finally connect this node output port to the input port of the Result node. So if the IsInAir variable has a true value, the transition will execute and the character will pass to the JumpStart state.

VisualScript of the transition between the Idle/Walk and JumpStart

VisualScript of the transition between the Idle/Walk and JumpStart

 

Save and exit the edition mode of the transition between the Idle/Walk and JumpStart and enter the edition mode of the JumpStart node to define which animation will be played in this case. Drag the JumpStart animation from the resources folder into the blueprint and connect it to the final node. The JumpStart and JumpEnd animations will be played only once which is different to what we did in the last tutorial where the Idle and walk animations are played in a loop. The idea is to start playing the JumpStart then pass to the JumpLoop that as its name suggest plays in a loop and finally play the JumpEnd animation only once. So select the JumpStart animation and in the property panel, uncheck the Loop option. Do the same for the JumpEnd animation.

VisualScript of JumpStart node

VisualScript of JumpStart node

 

Now we have to define the condition to pass from the JumpStart to the JumpLoop animation. For this task we will use a new node. We need to be able to detect when the JumpStart animation is about to end to know when to start playing the loop animation. Double click in the transition icon between JumpStart and JumpLoop. Once again we meet the Result node that we’re about to use but first we need to do an algorithm to define when the animation is near to end.

Let’s add a new node of the Time Remaining (Ratio) type to the Jump_Start Asset. This node informs us of how much time remains before the animation ends. Let’s add another node < float type, we will use it to know if an A parameter is smaller than a B one. Connect the output port of the TimeRemaining node to the upper input port of the comparison. For the second input port, we will use a manual value, in this case 0.1. Now,when the TimeRemaining is less than 0.1 (the animation is about to end), we transition to the JumpLoop state. In order to complete the transition setup, we still have to connect the comparison node output to the Result node. It should look like this: [caption id="" align="alignnone" width="630"]VisualScript of the transition between JumpStart and JumpLoop animationsVisualScript of the transition between JumpStart and JumpLoop animations[/caption]

 

Summing up, we periodically check if the StartJump animation is about to end by checking if the remaining time is less than 0.1. When this condition is fulfilled, the transition is executed.

Save and exit the transition’s edition mode and enter the state JumpLoop edition mode. Add the JumpLoop animation and connect it to the Result. Unlike with the JumpStart animation, with the JumpLoop animation, we need to play it in a loop because this is the one that plays while the character is falling, so check that the loop option is checked.

JumpLoop's node VisualScript

JumpLoop’s node VisualScript.

 

Let’s configure the last transition between the JumpLoop and JumpEnd nodes. JumpEnd, as its name suggests, is the animation played when the jump ends. The condition to transition to this state is that the character is close to the floor (it’s no longer in the air). That’s exactly what the isInAir variable represents. Double click on the transition icon between JumpLoop and JumpEnd and add the isInAir variable as GET. The problem is that we need to know when the character isn’t in the air so we need to use the isInAir negation, so we will add a NOT node type to the blueprint. This node type returns the input’s negation. Connect the NOT node to the isInAir node and this to the Result. Now, as soon as the character hits the floor, the character will transition to the JumpEnd state.

VisualScript of transition between JumpLoop and JumpEnd

VisualScript of transition between JumpLoop and JumpEnd.

 

Exit the transition’s edition mode and enter in the JumpEnd edition mode. Drag and connect to the Final Pose the JumpEnd animation and uncheck the loop option.

JumpEnd's node VisualScript

JumpEnd’s node VisualScript

 

Finally, we have to define the condition for when to transition back to the Idle/Walk state coming from the JumpEnd state. This animation represents the character already on the floor but still recovering from the fall. We need to wait until the JumpEnd animation is about to end to change to the initial state (Idle/Walk). Basically, we will repeat the steps we use in the transition between the JumpStart and JumpLoop. I hope you are able to do it by yourself… 😉 it should look like this:

VisualScript of transition between JumpEnd and Idle/Walk

VisualScript of transition between JumpEnd and Idle/Walk

 

Now we have completed the character state machine, but a small detail is still missing. We need to set the IsInAir variable value like we did earlier with the Speed variable.

Close the AnimGraph Editor and open the EventGraph. Add a GetMovementComponent node and connect the TryGetPawnOwner node that we created in our last tutorial to the input port of the GetMovementComponent node. Add another node and name it IsFalling and connect its output port of GetMovementComponent to the new node’s input port. Add the IsInAir variable in SET mode and connect the IsFalling output to the IsInAir input. Finally, connect the blank output port of the SET Speed to the SET IsInAir input for the algorithm continuity.

HeroAnimBlueprint's EventGraph modify it to set the IsInAir variable value when the character is in the air

HeroAnimBlueprint’s EventGraph modified to set the IsInAir variable value when the character is in the air

 

If you have read the previous tutorial, I’m sure you won’t have any problem understanding what we just did. We got the character’s MovementComponent, which contains an IsFalling method that returns a bool value denoting if the character is in the air or not. This IsFalling method is the one we will be using to set our IsInAir variable. Now we have the state machine for our character. Build the AnimationBlueprint and run the game. Press the space bar … now our hero jumps too :).

Character jumping with proper animation

Character jumping with proper animation

 

Implementing character’s running mechanism!!

Now our character knows how to walk, rest and jump, but a classic feature in side-scroller games is the ability to run to get more momentum and jump higher and further. Who doesn’t remember in one of Super Mario latest level the huge hole that can only be jumped with a great momentum, do you :)… So the next thing we will do is to add this skill to our character. Let’s implement it so that when the character is walking and the Shift key is pressed, the character will run.

First of all, open the editor and in the controls sections, add a new ActionBinding entry, name it Run and select the LeftShift key. Close the editor and open the HeroCharacter.cpp inside the SetupPlayerInputComponent method and add the following two lines:


//The Run entry is detected, when the Shift key is pressed we set that the ToggleRunState method should be call.
InputComponent->BindAction("Run", IE_Pressed, this, &AHeroCharacter::ToggleRunState);
   
//The Run entry is detected, when the Shift key is released we set that the ToggleRunState method should be call.
InputComponent->BindAction("Run", IE_Released, this, &AHeroCharacter::ToggleRunState);

Notice a tiny detail in this code. We are setting the BindAction for the Run entry twice and passing the same ToggleRunState method (that we will soon implement). The difference between the calls is that the second parameter specifies when the method gets called. The first case, IE_Pressed is fulfilled when the Shift key is pressed and IE_Release when it’s released. What we want to achieve is that when the key is pressed, the character runs and when the key is released, the character stops running and continues walking. Very similar to the Super Mario logic for the big jump!! :).

Now let’s implement the ToggleRunState method. Add the method’s declaration to the .h file:


/**
* Gets call when the engine detects the Run entry
* Change to the character run state.
*/
void ToggleRunState();

Go to the .cpp and add the following lines to the implementation:


/**
* Gets call when the engine detects the Run entry
* Change to the character run state.
*/
void AHeroCharacter::ToggleRunState()
{
//If the CharacterMovement's MaxWalkSpeed attribute is 400.f we increase it to 900.f to achieve that the character will  move faster.
//Otherwise we set it to 400 again so the character move in the walk speed range.
    if(CharacterMovement->MaxWalkSpeed == 400.0f)
        CharacterMovement->MaxWalkSpeed = 900.0f;
    else
        CharacterMovement->MaxWalkSpeed = 400.0f;
}

By default, the movement speed value is 400. When the shift key is pressed, we change the MaxWalkSpeed to 900, which causes the character to move faster. When the shift key is then released, the method is called again and we set the value back to 400, decreasing the movement speed.

If you want to test your knowledge, you can implement the run mechanism in the HeroCharacter’s Blueprint. If you accept thus challenge, remember first to delete the C++ code. It should looks like this:

Blueprint version of the run mechanism

Blueprint version of the run mechanism

 

I personally prefer to keep all the character’s logic in code, but this exercise may serve as practice to increase your skills in the Blueprint Editor, something that’s new for much of us and I’m sure caught your attention :). I also recommend to expose the max and min values of the MaxWalkSpeed so it can be modifiable from the Editor and to avoid going to the code to change its values. Remember, in order to make variable values modifiable via the editor, you have to use the UPROPERTY macro, but this time I won’t show you how I did it so you must check how we did it previously and use your imagination. I’m sure you can do it 😉

Build and play the game. Let’s try it out. While you’re walking, press the shift key and you will see how the character moves much faster. We’re not quite finished yet because the character still uses the walk animation while running so it looks weird.

Adding the run animation to the character

Let’s add another animation to our character – the run animation. We will use the Idle/Walk state created previously to add the run animation. We will also use the same node to blend between the animations. An amazing feature of this type of node is that we can add more than two animations. The idea is to modify it to set three control points: the first one at the start of the graph will use Idle animation, the second in the middle of the graph will use the Walk animation, and the third at the end of the graph for the Run animation.

Import Run.FBX from your resources, open the IdleWalkBlendSpace1D created in the last tutorial, change the X Axis Range property to 900 (the movement value of the character when it’s running), and click the Apply Parameter Changes. Now add the Idle animation at the start, the Walk animation in the middle, and the Run animation at the end. Make sure the Enable Preview BlendSpace option is checked and move the cursor over the graph to see the blend between the animations according to the speed value. Pretty cool and easy, ehhh???

New IdleWalkBlendSpace1D configuration for the Idle/Walk/Run state

New IdleWalkBlendSpace1D configuration for the Idle/Walk/Run state

 

Save, play the game and try to run. Now our character walks and runs perfectly.

Running character pressing the D+Shift keys

Running character pressing the D+Shift keys

 

Adding coins in the scene for the character collect.

We already have our character walking, running and jumping through the scene in a side-scroller style, but we need to add some purpose to the game because wandering around isn’t much fun. Let’s try to improve it a little by adding some coins in the scene for the character to collect, a common feature in this style of game. In next tutorials, we will see what our character wins with these coins.

First, we need a coin model. I’m sure you can find several StaticMesh that you can use as coins from the MarketPlace. I strongly recommend you take some time to visit the MarketPlace. I’m sure that you will find a lot of things that you will love and many of them are FREE!! :D.

Anyway, you can download the FBX of a very simple coin here :S Its not amazing but it works for this tutorial. Import the FBX file to the project (I created a Coin folder). In the importing window, you will notice that Unreal detects that this is a StaticMesh. Expand the advanced options and select the materials and texture options. This way, we’re also importing the model’s material. That is also extremely simple.

Now with the resource imported, let’s create the C++ class that encapsulates all the coin logic. Create a new class in the Editor and named it Coin. This class will inherit from Actor. Select Yes when the Editor asks you if you want to open the class in the IDE. Modify the .h file so it looks like the following:


/** Represents a coins. The character can collect them by colliding with them. */
UCLASS()
class ACoin : public AActor
{
GENERATED_UCLASS_BODY()
   
    /**
     * USphereComponent is sphere shape component generally used to detect simple collisions
     * This will be the root component of the coin and with it we will detect the collisions between the character and the coins.
     */
     UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Coin)
    TSubobjectPtr<USphereComponent> BaseCollisionComponent;
   
    /** Coin's StaticMesh, we used previously in the Character. We store the coin's StaticMesh instance. */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Coin)
    TSubobjectPtr<UStaticMeshComponent> CoinMesh;
   
    /**  Boolean variable to enable/disable the coin */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Coin)
    bool bIsActive;
};

I recommend you pay attention to the code comments because I always point out which function has every line. Basically, we define the BaseCollisionComponent attribute to be the root component of our coin which we will use to detect collision. The other attribute is the UStaticMeshComponent which is used to define the StaticMesh that represents the coin in the level. The last one is the bIsActive we will used as a flag to disable the coin when the user collides with it.

Go to the .cpp file and modify the constructor to match the following code:


ACoin::ACoin(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
    //Create the USphereComponent instance.
    BaseCollisionComponent = PCIP.CreateDefaultSubobject<USphereComponent>(this, TEXT("BaseSphereComponent"));
   
    //Initialize the Actor's RootComponent with the USphereComponent.
    RootComponent = BaseCollisionComponent;
   
    //Create the UStaticMeshComponent instance
    CoinMesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("CoinMesh"));
   
    //We add the UStaticMeshComponent as child of the root component
    CoinMesh->AttachTo(RootComponent);
   
    //By default the coin will be active
    bIsActive = true;
}

We instantiate the USphereComponent as the coin’s root component. We also create the UStaticMeshComponent instance. We will soon configure it from the Editor and then added it to the RootComponent. Finally, we set the bIsActive variable to true because we want the coin to be active when it’s created.

Adding coins to the scene

Let’s create the Coin’s Blueprint as we did with the Character in the last tutorial. In the ContentBrowser, select the Coin folder, then right click and select the Blueprint option. Finally, set Coin as the base class and name it CoinBlueprint. As you will see, it contains the same components that we define in the USphereComponent constructor, for instance, the RootComponent and the UStaticMeshComponent. Unfold the CoinMesh and select the StaticMesh imported for the coin.

CoinBlueprint component's section. Adding the StaticMesh component

CoinBlueprint component’s section. Adding the StaticMesh component.

 

Now we will add some coins to the scene. Find the CoinBlueprint in the ContentBrowser and drag it into the level at the position where you want to place a coin. This must be a place reachable by the character. It’s important to remember that in this style of game, the character is placed on a fixed Y axis, so you must place the coins at the same Y value as the character in order for him to collide with the coins.

This is the simple level I made:

Level in edition mode with coins added.

Level in edition mode with coins added.

 

We added the coins to the level, but so far they don’t do anything. If you walk into the coins, nothing happens. Besides, the coins look very bad because they’re static. Let’s add some life to them.

Collision mechanism to collect the coins

At the moment, the character hits the coins but there isn’t any feedback. Let’s add the collision detection that, as its name suggests, will detect the collision between the character and the coins. We will have a “Collected coins” variable that we will increase when the character collides with a coin. We will create and call the OnCollected() method in the Coin class where we will disable the coin, delete it from the scene and print a log on the screen to debug the algorithm.

Open the HeroCharacter.h and add the following declarations:


    /** Character's amount of coins collected */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Coins)
    int32 CoinsCollected;

    /** It's called constantly in the character's Tick to check if its colliding with a coin */
    void CollectCoins();
   
    /**
     * Gets executed automatically by the engine every frame
     * @param DeltaSeconds the difference in seconds between the last and current frame.
     */
    virtual void Tick(float DeltaSeconds) OVERRIDE;

Before we get deep in code, let’s theorise on the functionality that we will use. Every Actor in UE4 implement the Tick method. This method is called automatically by the engine every frame. If you are reading this tutorial, you are probably familiar with game development, at least with its theory. If so, you will know this method’s significance. Basically, all of the Actor’s algorithms that must run constantly will be called inside this method. The method receives a float as a parameter. This is the amount of time, in seconds, that has passed since the last tick/frame. This value is commonly used in a variety of tasks. For instance, it can be used as a multiplier when modifying the character’s position or rotation in order to achieve an execution depending on the framerate as to avoid gaps in case that the framerate decreases.

So we will call the CollectCoins method in the Tick method so it runs constantly. Internally, this method checks if a coin is inside the Character’s CapsuleComponent. If it is, that means that the character is colliding with that coin do we should trigger our collision mechanism.

Open the HeroCharacter.cpp file. At the end of the constructor implementation, add the CoinsCollected = 0 line. This causes the CoinsCollected variable to be set to zero when the character is create and is done because hasn’t collected any coins. Add the CollectCoins and Tick methods:


/** It's called constantly in the character's Tick method to check if is colliding with a coin */
void AHeroCharacter::CollectCoins()
{
    //AActors array to save temporary all the actors that are colliding with the character
TArray<AActor*> CollectedActors;
   
    //GetOverlappingActors method of the CapsuleComponent. This method returns all the actors colliding with the character in the array we pass as parameter.
    //All the objects that are currently inside the capsule.
    CapsuleComponent->GetOverlappingActors(CollectedActors);
   
    //We iterate through all the objects inside the CapsuleComponent.
    for(int32 i = 0; i < CollectedActors.Num(); i++)
    {
        //We have to cast the array elements to ACoin because the array is declared as AActors.
        ACoin *Coin = Cast<ACoin>(CollectedActors[i]);
       
        //We make sure that the coin is active and that the Destroy method hasn't been called
        if(Coin != NULL && !Coin->IsPendingKill() && Coin->bIsActive)
        {
            //We increase the coins collected amount.
            CoinsCollected++;
           
            //Finally, we call the OnCollected method of the Coin class to execute all the collect coin logic.
            Coin->OnCollected();
        }
    }
}

/**
* Gets executed automatically by the engine every frame
* @param DeltaSeconds the difference in seconds between the last and the current frame.
*/
void AHeroCharacter::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    //In every update is called the CollectCoin to constantly check if it's colliding with a coin.
    CollectCoins();
}

Take a minute to read every line’s comments to really understand the collision detection process. This is a very simple collision detection but it’s enough to understand how to work with it in UE4 :). It’s important to clarify that inside the CollectCoins method we have a reference to the ACoin class created by us. To avoid errors, we have to add #include “Coin.h” declaration at the top of the .cpp file below the #include “UE4Demo.h” line.

Only one small thing is left. If you try to build now you will get an error because when a collision is detected, the OnCollected method of the Coin class is called and we haven’t implemented this method yet. Let’s do that now:

Add the method’s declaration to the .h file and the implementation in the .cpp.


/** It's called when a collision is detected */
void ACoin::OnCollected()
{
    //To Debug we print a log on the screen
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Coin Collected !!");
   
    //We change to false the bIsActive flag
    bIsActive = false;
   
    //We call the Actor's Destroy method, to remove the coin from the scene
    Destroy();
}

This method will be called when a collision is detected. First we use the AddActorLocalRotation method of the framework’s GEngine object. This method is very handy because it allows us to print messages in a specific colour at a certain amount of time on the screen. To debug our code in runtime is very useful. Here we print Coin Collected when the character collides with a coin. Besides that, we set the bIsActive variable as false and we remove the coin from the scene using the Actor’s Destroy method.

Build and run the game one more time. Now walk into a coin … Very nice!! When the character passes through a coin, the coin is removed from the scene. Internally, the amount of coins collected is increased (at the moment there isn’t any visual feedback regarding this value) and then we print the temporary Coin Collected¡! log to the screen.

“Debugging” the scene collisions

If you are good at paying attention to small details, I’m sure you will notice a small problem with this collision mechanism. Try to reach a coin slowly, you will notice that the collision occur before the character actually touches the coin. To find the problem, UE4 provides us with an amazing command that allows us to check the collision component of an actor at runtime. Run the game one more time and pay attention in the upper-right corner of the Editor. You’ll notice there is a text field that allows us to write commands. Write in the field: show COLLISION and press the Enter key. Immediately, the collision component appears over every actor .

Run time game with the show COLLISION command active to debug the Actor's collision component.

Run time game with the ‘show COLLISION’ command active to debug the Actor’s collision component.

 

Notice two things that could the cause the problem. First, the coin component is bigger than the coin itself. Second, the character’s capsule radius could be reduced. End the game execution and open the CoinBlueprint then select ROOT in the components mode. In the Details panel, search for the Shape section that holds the sphere radius. Change its value to 15 and check out the preview showing how the sphere surrounds the coin. Notice how this adjustment better fits the model. Save and try again. You will see how the collision detection has improved as now the character has to be very close to catch the coin.

Modifying the sphere component radius to adjust the collision detection

Modifying the sphere component radius to adjust the collision detection

 

You can do the same with the character’s capsule if you want to continue improving the collision mechanism. It’s important to mention that this value can be defined in the class constructor by code. Try to do it by yourself to practice.

Rotating coins on its own axis.

To add some life to the coins, we will rotate them on their own Y axis. Close the editor, open the Coin.h class and add the following lines:


    /** Coin rotation factor */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rotation")
    FRotator RotationRate;
   
    virtual void Tick(float DeltaTime) OVERRIDE;

We simply add the Tick method and a new RotationRate attribute of FRotator type. This is the rotation vector we will use to rotate the coin in every Tick call. Actually, there is no need to declare an attribute but in doing so, we gain the opportunity to configure the rotation factor in the editor, thanks to the UPROPERTY macro.

Go to Coin.cpp file. At the end of the constructor, add this two lines:


    //Initialize the coin rotation factor in each update
    RotationRate = FRotator(0.0f, 180.0f, 0.0f);
   
    //Enable the Tick method of this Actor
    PrimaryActorTick.bCanEverTick = true;

The first line is the rotation vector’s initialization. By default, the Tick method isn’t called automatically in an Actor inherited class such as our Coin class. To enable the call to the Tick method in this class, it’s necessary to set the PrimaryActorTick.bCanEverTick attribute as true.

Very well, now add the Tick method’s implementation:


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

    //Adds a rotation factor to the coin in each Tick call
    AddActorLocalRotation(this->RotationRate * DeltaTime, false);
}

The code is very simple. Basically we add a rotation factor thanks to the AddActorLocalRotation, in each update.

Ready!! Build, run and test. Super nice, right!! The coins are constantly rotating in the scene, waiting to be caught :).

Implementing the coin logic in the Blueprint.

As I already mentioned in the last tutorial, the decision of whether to use the code or the Editor is up to the individual. I personally follow these two rules of thumb. 1. Implement all the related things only in one place (for maintenance purpose). In other words, don’t have half of the logic in code and the other half in the Blueprint. 2. If the Actor logic is very simple like it is for this coin, the ideal solution is to use the Blueprint. However, when the thing needed to be implemented is large and complex, I personally prefer to use C++.

You could have some practice implementing the coin logic in the Blueprint. Do you dare? It should look something like:

Coin VisualScript

Coin VisualScript

 

Notice that we implement what happens when the OnCollected method is called in the Blueprint. We still have to add in the method’s declaration, the UFUNCTION(BlueprintNativeEvent) macro, or the UFUNCTION(BlueprintImplementableEvent). Next I will explain both:

UFUNCTION(BlueprintImplementableEvent) is designed to be overridden by a blueprint. Do not provide a body for this function; the automatically generated code will include a thunk that calls ProcessEvent to execute the overridden body.

[UFUNCTION(BlueprintNativeEvent) macro]. This function is designed to be overridden by a blueprint, but also has a native implementation. Provide a body named [FunctionName]_Implementation instead of [FunctionName]; the automatically generated code will include a thunk that calls the implementation method when necessary.

For example, if you want to test the OnCollected method, the definition will be like this:


UFUNCTION(BlueprintNativeEvent)
void OnCollected();

And the implementation should look like:


void ACoin::OnCollected_Implementation()
{
    //As Debug we print a log on the screen
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Coin Collected !!");
   
    //We change the bIsActive flag to false
    bIsActive = false;
   
    //We remove the coin from the scene with the Destroy method
    Destroy();
}

The silver lining is that we can have our code implementation, but we can also override this implementation in the Blueprint … cool, right? UE4 stuffs!! :). Try the coin logic implemented in VisualScript in order to gain confidence in the Blueprint Editor, one of the wonders of UE4.

Conclusion

Well, I think it’s time to call it a day. In this second tutorial, we did several new things. Among them, we made a simple collision detection mechanism We implemented the Tick method. We added the run and jump animations to the character. We configured our game camera to the 3D Side-Scroller style. We saw some variables and macro class functions for the integration between the code and the Blueprint Editor, etc.

In next tutorial, we will continue to build our game. We will add a HUD so the user knows the number of coins collected, and the time available to achieve some goal. We will define the GameMode, the win and lose conditions and so much more ;).

I hope you found this tutorial useful. If you did, please leave me your comments and share this tutorial with the rest of your friends who are also passionate about game development with UE4. Till next time!!… Bye.

Cómo causar daño a un personaje en UE4 – Parte 2

En el tutorial pasado vimos una introducción al AnimComposite y el AnimMontage en Unreal Engine 4 y dejamos a nuestro personaje con las habilidades necesarias para dar puñetazos. En este tutorial vamos a usar esas habilidades para golpear a otro personaje, causándole daño hasta que su salud llegue a 0 y muera.

Este simple ejemplo nos permitirá ver varias cosas nuevas:

– Introducción a los mecanismo de colisión que nos brinda UE4
– El uso del MarketPlace.
– El uso del Construction Script en los blueprints
– Cómo aplicar daño a un personaje
– Introducción al trabajo con efectos de sonido en Unreal Engine 4
– Cómo reproducir un AnimSequence directamente desde código
– Como eliminar un Actor del nivel cuando ya no se va a usar más

Y muchas cosas más 😉 … así que, manos a la obra !!

Introducción a los mecanismos de colisión en Unreal Engine 4

En el segundo tutorial vimos un ejemplo simple del trabajo con las colisiones entre dos objetos, cuando implementamos la funcionalidad para que el personaje pudiera recolectar las monedas dispersas por el terreo. Vamos en este tutorial a profundizar un poco en la teoría detrás de las colisiones en UE4.

Unreal Engine 4 tiene un potente y flexible mecanismo para el manejo de colisiones entre los elementos del juego. Cada objeto que puede colisionar con otro es de un “Object Type“ y se le define cómo responderá a la colisión con los otros Object Types de tres formas distintas: si lo Ignorará, si se superpondrán o si lo bloqueará. Los Object Types existentes son:

WorldStatic: Los Volúmenes existentes en el juego, y los objetos estáticos del nivel, por ejemplo: Una piedra o una pared deben ser WorldStatic

WorldDynamic: Los Actores dinámicos (los que se mueven) a parte a los Pawn, PhysicsBodies y Vehicles (que son Objet Type específicos). Serían por ejemplo, una plataforma que se mueve de un lado a otro, un elevador, etc.

Pawn: Los personajes, estos ya los conoces 😉

PhysicsBody: Los objetos físicos. El trabajo con objetos físicos los veremos en próximos tutoriales. Básicamente son objetos que se les puede definir para que se comporten físicamente real. Por ejemplo, que los afecta la gravedad, su masa, fuerzas o impulsos que se les aplica, etc. Los objetos de este tipo en el nivel deben tener como Object Type, PhysicsBody.

Vehicle: Este es bastante claro, no ? :) .

Destructible: Actores destructibles. Estos los veremos también en próximos tutoriales y de seguro te va a encantar jugar un poco con ellos. Básicamente son objetos que los podemos configurar para que se fraccionen y se rompan cuando reciban un impacto, un efecto genial y que siempre gusta mucho.

Bien, ya sabemos los distintos Object Type que nos da UE4, ahora vamos a investigar un poco como está configurado por defecto nuestro personaje protagónico para responder a las colisiones. Abre el Blueprint del personaje, selecciona el modo Components y selecciona el componente [ROOT]CapsuleComponent. En el panel de detalles muévete hasta la sección Collision y despliega la propiedad Collision Presets.

Blueprint del personaje en el modo Components donde se muestra la configuración de Collision del [ROOT] CapsuleComponent

Blueprint del personaje en el modo Components donde se muestra la configuración de Collision del [ROOT] CapsuleComponent

 

Desde esta sección podemos configurar el Object Type de este objeto y cómo reaccionará a las colisiones con los otros elementos del juego. Fíjate en la propiedad Collision Presets, en este caso tiene seleccionado Pawn. El UE4 por defecto nos trae un grupo de configuraciones predefinidas que son comunes en los juegos y que podemos seleccionar, y así no tenemos que configurar siempre manualmente como reaccionará este objeto con los otros con los que colisione. Si expandes esta sección verás que todas las propiedades están bloqueadas y con una configuración predefinida, prueba variar la selección de Collision Presets a otro que no sea Pawn, notarás que cambia la configuración. Por supuesto, siempre podemos seleccionar Custom … y settear la configuración que queramos específicamente para cada Object Type.

La configuración de colisión que tiene el CapsuleComponent del personaje, predefinida por el Preset Pawn es la siguiente:

Primero tenemos la propiedad Collision Enabled en la que se pueden seleccionar tres posibles valores:

No Collision: Cero colisión, son ignoradas totalmente las colisiones en las que interviene este objeto.
No Physics Collision: Las colisiones de este objeto solo se tienen en cuenta para raycasts y overlaps (los veremos más adelante)
Collision Enabled: Para responder a ambos tipos de colisiones: con simulación física y sin ella.

Debajo tenemos el Object Type para definirle a este elemento. En este caso es Pawn.

A continuación tenemos una especie de tabla. Las filas son cada uno de los Object Type y cada columna representa un tipo de Collision Response que tenemos para reaccionar a una colisión, estos son:

Ignore: Ignorará completamente la colisión. Lo que quiere decir que para el Object Type que se le marque Ignore, cuando este objeto colisione con él, ignorará por completo esto y lo traspasará.

Overlap: Overlap es igual a Ignore, o sea, los objetos se traspasan, pero este tiene una particularidad. Si te fijas, al inicio de la sección Collision tenemos dos atributos a marcar. Simulation Generate Hit Event y Generate Overlap Event. Si le marcamos para un determinado Object Type que maneja la colisión con Overlap estos se traspasarán, pero si la opción Generate Overlap Event está en true, en el momento de la colisión se generará un evento que podemos intervenir desde el Blueprint o C++ y ejecutar una acción determinada. Veremos un uso de esto más adelante. Además de la opción Generate Overlap Events tenemos la propiedad Simulation Generate Hit Events. Este check es semejante al otro pero lo usamos cuando el objeto es físico. Habilita para que se generen eventos de tipo Hit cuando el objeto físico colisiona con otro. Veremos su utilidad en próximos tutoriales.

Block: Con el objeto que se defina para que responda a la colisión con Block no podrá ser atravesado. En el caso de la cápsula de colisión del Pawn verás que para todos los Object Type el Collision Response está en Block, para evitar que el personaje atraviese las cosas.

Por último, un detalle importante, fíjate que las filas están separadas en dos bloques Trace Response y Object Response. El primero nos permite definir como reaccionará el objeto a las colisiones con los Traces, estos son básicamente rayos invisibles que podemos usar para determinar si algún objeto colisiona con ese rayo y tiene montón de utilidades. Veremos uso de estos en próximos tutoriales.

Te recomiendo que le dediques un tiempo a revisar la configuración de colisión para cada uno de los Collision Presets y además la configuración de colisión para cada uno de los componentes del personaje, otra configuración importante a tener en cuenta y entender es la del Mesh del personaje que usa como Collision Presets: CharacterMesh

Configuración de las propiedades de Collision para el Mesh del personaje.

Configuración de las propiedades de Collision para el Mesh del personaje.

 

Muy bien, ya con esta teoría de nuestro lado, podemos pasar a implementar la funcionalidad para poder golpear al lanzar un puñetazo. La lógica detrás de esto sería: detectar cuando el puño colisiona con el otro personaje y en ese momento implementar lo necesario para reaccionar al golpe. Vamos a agregar al nivel otro personaje que nos servirá como monigote para practicar nuestros golpes.

Con los recursos que tenemos ahora mismo, poco podemos hacer, pero aquí viene otro de los enormes regalos que tenemos al usar Unreal Engine: El Marketplace !!. El equipo de Epic, por si fuera poco el poner este fenomenal motor en nuestras manos, también nos brinda acceso al MarketPlace, la zona en donde podrás encontrar una enorme cantidad de recursos para tus proyectos, tus prototipos para aprender, etc. Es un lugar que al menos todas las semanas deberías darle un recorrido para ver que te trae de nuevo.

Introducción al Marketplace

Para usar el Marketplace tienes que tener tu subscripción válida y el Unreal Launcher, que si no lo tienes, lo puedes descargar haciendo clic en el editor en la barra superior en el botón MarketPlace:

tuto6_imagen_01

Una vez que abras el Unreal Launcher tendrás la pantalla de login. Pon tu usuario y password y … “Bienvenido al paraíso“ !! :)

Captura del Marketplace

Captura del Marketplace

 

Tómate unos minutos y revísalo, veras el montón de cosas que encontrarás, de seguro lo querrás bajar todo :) .. . . sí, es verdad, no todo es gratis, de hecho, la mayoría de las cosas son de pago, pero vamos !! mira el precio y compáralo con todo el trabajo que te ahorrarás, la ventaja es enorme !!. De cualquier forma, no te preocupes, lo que vamos a necesitar en nuestros tutoriales es gratis :). Busca aquí el Animation Pack y descárgalo (está marcado en rojo en la imagen anterior). El Animation Pack es un paquete con un montón de animaciones con el mismo modelo que estamos usando en nuestros tutoriales.

Después de descargarlo, en la sección de Library podrás tener acceso a todo lo que descargues y desde ahí lo podrás agregar al proyecto. Agrega el Animation Pack al proyecto, verás que se te creará en el Content Browser una carpeta de nombre AnimStarterPack y dentro de ella, todo el contenido de este paquete. Tómate unos minutos y abre cada una de las animaciones para que veas todo lo que tenemos ahora para nuestros tutos :). Dentro de la carpeta AnimStarterPack además de las animaciones ya importadas tendrás la carpeta Character. Dentro de esta carpeta está el Blueprint, el AnimBlueprint el Skeletal Mesh y el resto de los recursos necesarios. Aunque de momento no usaremos el Blueprint del Character que trae por defecto el AnimStarterPack te recomiendo que le des un vistazo y lo estudies un poquito.

Creando un personaje para golpear

Voy a pasar por estos pasos bastante rápido, porque si has seguido los tutoriales no tendrás problema en hacer esto por tu cuenta.

Crea una carpeta en el Content Browser que se llame BlueEnemy. Crea un nuevo Blueprint que herede de Character y ponle de nombre BlueEnemyBlueprint. Crea un AnimationBlueprint para el esqueleto HeroTPP_Skeleton que está en /Game/AnimStarterPack y ponle BlueEnemyAnimBlueprint de nombre. Ahora abre el BlueEnemyBlueprint selecciona el Modo Default y define el Mesh de este Character con el Mesh del AnimStarterPack y el Animation Mode en Use Animation Blueprint y el BlueEnemyAnimBlueprint que acabamos de crear.

Modo Default del BlueEnemyBlueprint

Modo Default del BlueEnemyBlueprint

 

Pasa ahora para la sección Components y mueve el Mesh para que quede dentro de la cápsula y en la dirección correcta

Modo Components del BlueEnemyBlueprint

Modo Components del BlueEnemyBlueprint

 

Ahora pasa al modo Graph para abrir el Blueprint de este personaje y créale una variables nueva de nombre Health y de tipo INT y en valor por defecto ponle 100. Fíjate un detallito interesante, al crear una variable desde el blueprint puedes definirle un Tooltip. Este Tootip es un texto descriptivo para la variable que se ve cuando se pone el cursor sobre ella en el panel My Blueprint.

La variable Health será quien defina la salud de este personaje, cada vez que le demos un puñetazo perderá salud hasta que el valor de esta variable llegue a cero, cuando llega a cero muere.

Ahora abre el AnimationBlueprint para este personaje y crea una maquina de estado súper simple, solo con el estado Idle.

Maquina de estado muy simple para el BlueEnemyCharacter

Maquina de estado muy simple para el BlueEnemyCharacter

 

Vamos a aprovechar esta situación para poner otro ejemplo del recién aprendido AnimMontage. En realidad esto que haremos no es necesariamente con el AnimMontage, pero creo que va bien practicar un poquito lo que acabamos de aprender para que se pegue, por eso vamos a hacerlo así :) Crea un nuevo AnimMontage como lo vimos en el tutorial pasado, dale de nombre HitAnimMontage y agrégale las animaciones Hit_React_1, Hit_React_2 y Hit_React_3 que tenemos en el AnimStarterKit. Configúralo para que te quede de la siguiente forma:

HitAnimMontage que usaremos para reproducir aleatoriamente una animación de impacto en el personaje cada vez que reciba un puñetazo.

HitAnimMontage que usaremos para reproducir aleatoriamente una animación de impacto en el personaje cada vez que reciba un puñetazo.

 

Por último modifica el AnimGraph para agregarle este Slot entre el State Machine y el Final Animation Pose.

tuto6_imagen_10

Ya tenemos todo lo necesario para jugar un poco con este personaje medio monigote y digo medio monigote porque básicamente lo que hará es estar en reposo, cuando reciba un puñetazo expresará su dolor reproduciendo una animación y cuando su salud llegue a cero morirá.

Para poder ver bien las colisiones entre ambos personajes podemos usar un pequeño truco. Abre el BlueEnemyBlueprint selecciona el modo Componentes, selecciona [ROOT]Capsule Component y en el panel detalles muévete hasta la sección Rendering y desmarca la propiedad Hidden In Game. Has esto mismo para el personaje protagónico. Esto nos ayudará a ver en tiempo de ejecución esta cápsula y nos ayuda a revisar en detalles las colisiones.

Pero antes de probar esto, tenemos un detallito. El componente que tiene este personaje para colisionar y bloquear a los objetos, es una cápsula. En nuestro juego solo nos desplazamos en un solo eje, pero cuando caminas hacia este otro personaje y comienzan a colisionar y a bloquearse, si intentas seguir caminando nuestro personaje patinará alrededor de la cápsula rompiendo el modo de desplazamiento de nuestro scroll-side. Pruébalo para que lo veas mejor. Para evitar esto, lo que hice fue agregar un Box Component al personaje que encierre a la cápsula y las propiedades de colisión las configuro igual a la cápsula, con el Preset: Pawn. Con esto, al ser recta la cara de la caja, se evita el problema del desplazamiento forzado.

Listo !!, agrega este personaje al escenario, recuerda que como nuestro juego es un scroll side y el personaje principal solamente se mueve hacia la derecha o la izquierda en un solo eje, para que se puedan encontrar ambos tienen que estar alineados.

Guarda, compila, ejecuta el juego y muévete en dirección al BlueEnemy. Cuando los dos componentes llegan a colisionar, ya no te puedes mover más. Si recuerdas cuando miramos la configuración de colisión para la cápsula, esta tiene marcado como Collision Response para todos los elementos: BLOCK. Por eso es que al colisionar estos dos elementos no se pueden traspasar.

Ambos personajes en el punto donde colisionan los componentes que los encierran. Como ambos están configurados como Block, aunque intentes seguir moviéndote en esa dirección no podrás avanzar más.

Ambos personajes en el punto donde colisionan los componentes que los encierran. Como ambos están configurados como Block, aunque intentes seguir moviéndote en esa dirección no podrás avanzar más.

 

Bien, eso está perfecto. . . ahora, lanza un puñetazo presionando la tecla R. Como notarás, no pasará absolutamente nada, y en este caso la mano traspasa el Mesh del otro personaje. Tenemos que lograr detectar la colisión del puño del personaje con el Mesh del enemigo. Para esto vamos a irnos por una solución muy simple, pero suficiente para nuestro juego. Dicho sea de paso, esta solución fue tomada del los video-tutoriales de Epic Games que te comenté en el tutorial pasado y que aprovecho para recomendártelos de nuevo :)

Vamos a agregar dos esferas que estarán ancladas a los puños del personaje. Cuando se detecte la colisión de una de esas esferas con el Mesh del otro personaje, es que estamos golpeándolo.

Agregando dos Sphere Components anclados a las manos del personaje para detectar la colisión cuando se lance un puñetazo.

Abre el Blueprint de nuestro héroe en el modo Components. Fíjate que en el panel Components encima de la jerarquía de componentes que forman parte de nuestro personaje, hay un ComboBox que dice Add Component. Desde este combobox podemos agregar nuevos componentes al personaje. Despliégalo y selecciona una Sphere, repite el proceso y agrega otra. Mueve las esferas para que queden más o menos sobre cada una de las manos del personaje, no te tiene que quedar perfecto esto es solo temporal. Cámbiale los nombres a esos componentes a PunchRightComponent y PunchLeftComponent. Puedes seleccionar las dos dando clic en una y con la tecla Ctrl presionada da clic en la otra. De esta forma podrás modificar una misma propiedad en ambos componentes al mismo tiempo. Muévete en el panel Details a la sección Rendering y desmárcale Hidden in Game. En la sección Shape, a la propiedad Sphere radius dale el valor de 15. Recuerda que el Hidden In Game es temporal, solo para poder ver el componente en el juego y poder revisar mejor las colisiones.

Blueprint del personaje principal en el modo Components con las dos esferas agregadas

Blueprint del personaje principal en el modo Components con las dos esferas agregadas

 

En este punto las esferas están agregadas como componente del personaje, pero tenemos que anclarlas a las manos del mismo para que se muevan junto con estas cuando se lance el puñetazo.

Hasta ahora solo hemos usado la hoja Event Graph del blueprint del personaje, pero como ya habrás notado, también contamos con una hoja en blanco de nombre Construction Script. Todo algoritmo que programemos aquí mediante visualscript se ejecutará en la construcción del objeto. Vendría jugando como el papel del constructor de nuestra clase. Modifícalo para que te quede de la siguiente forma:

Construction Script del HeroCharacterBlueprint para anclar los PunchComponents a los huesos de la mano del personaje.

Construction Script del HeroCharacterBlueprint para anclar los PunchComponents a los huesos de la mano del personaje.

 

Es simple lo que hacemos aquí, incluso ya lo habíamos hecho anteriormente pero desde C++. Hacemos un AttachTo de un componente a otro, como mismo hicimos con la cámara y el SpringArm en el segundo tutorial. El nodo Attach To nos permite anclar un componente a otro y le podemos especificar también el Socket al que lo anclaremos. Todo lo relacionado con los Sockets lo veremos en próximos tutoriales, de momento vasta con saber que son puntos en el objeto que podemos usarlos para anclar otro objeto. Fíjate que en el parámetro In Socket Name escribimos directamente hand_l y hand_r para cada uno de las esferas . . . uuumm, de seguro te imaginas que es esto eh ? 😉 . . . pues sí, podemos usar como socket, cualquiera de los huesos del esqueleto que usa este Mesh. Si abres el esqueleto de este personaje verás que los huesos de la mano se llaman hand_l y hand_r .

El último parámetro del nodo AttachTo es el Attach Type y nos permite definir mediante tres valores de enum como se afectará la posición y rotación de este elemento respecto al padre. En nuestro caso queremos que lo siga totalmente, así que selecciona la opción Snap to Target.

En este punto me gustaría comentarte algo. Este es el tipo de cosas que yo en lo personal prefiero hacerlas en C++, de hecho, si te fijas en nuestra clase Character toda la creación de los componentes y los Attach To los hacemos desde C++. Quise en este tutorial hacerlo mediante blueprint para ver un ejemplo de esta vía. Un muy buen ejercicio para que sigas logrando soltura con el framework de clases es que intentes hacer esto mismo pero desde C++ … y esta vez no te dejaré ninguna pista :)

Pues bien, compila, guarda y pasa al modo Components, verás que ahora salen las esferas ancladas perfectamente a las manos del personaje. Esto pasó porque al compilar se ejecuta el construction script y esta sección de componentes se actualiza. Genial verdad !?

Modo Components del Blueprint del Character con los PunchComponents anclados a la mano del personaje.

Modo Components del Blueprint del Character con los PunchComponents anclados a la mano del personaje.

 

Muy bien, casi terminamos aquí, solo nos queda un detalle. Para detectar la colisión entre este personaje y el enemigo vamos a usar el Evento Overlap que hablamos al inicio. O sea, podemos saber cuando dos elementos se superponen y lanzar un evento en ese preciso momento, es esa la técnica que vamos a usar para detectar cuando golpeamos al otro personaje, pero para hacer esto bien, tenemos que hacer una modificación en las propiedades de colisión de ambos PunchComponents.

Selecciona los dos, PunchRightComponent y PunchLeftComponent desde el modo Components del Blueprint del personaje y en Collision Presets selecciona OverlapAll y desmarca el check: Generate Overlap Event. Pero te preguntarás ¿Porqué desmarcar esta opción sin en realidad necesitamos que se dispare el evento cuando se detecte el overlap?. Esto es verdad, pero si desde ahora dejamos esto en true, constantemente estos componentes generarían el evento y si por ejemplo se pasa por al lado del personaje aunque sea caminando, se dispararía el evento. Como es lógico, no es esto lo que queremos, solo queremos que se detecte el evento si se está golpeando. Por lo que dinámicamente vamos a poner esta propiedad en true para cada brazo en el momento preciso y para esto vamos a usar los BranchPoints que tenemos definido en el PunchingAnimMontage.

Abre el PunchingAnimMontage y fíjate que tenemos dos BranchPoints que creamos en el tutorial pasado. Ajusta la posición de cada BranchPoint más o menos a la mitad de la animación si no lo tienes así:

tuto6_imagen_12.1

Si te fijas con el timeline de la animación, cada evento se dispararía ya cuando vamos a dar el golpe y en este punto es cuando activaremos el Generate Overlap Event de la mano correspondiente y desactivamos el de la otra mano.

Abre ahora el AnimationBlueprint del personaje y modifica donde intervenimos estos eventos para que te quede de la siguiente forma:

Trozo del AnimationBlueprint de nuestro personaje donde intervenimos el evento de los BranchPoint del PunchingAnimMontage y agregamos para activar o desactivar según corresponda la propiedad Generate Overlap Event de los PunchComponents.

Trozo del AnimationBlueprint de nuestro personaje donde intervenimos el evento de los BranchPoint del PunchingAnimMontage y agregamos para activar o desactivar según corresponda la propiedad Generate Overlap Event de los PunchComponents.

 

Por último, para que el evento se dispare, necesitamos habilitar el Generate Overlap Event también en el otro personaje. Abre el BlueEnemyBlueprint en el Modo Components selecciona el Mesh y márcale la propiedad Generate Overlap Event ya que queremos detectar cuando uno de los dos PunchComponents del personaje colisiona con el Mesh de este otro.

BlueEnemyBlueprint en modo Components con la propiedad Generate Overlap Event para el Mesh en true.

BlueEnemyBlueprint en modo Components con la propiedad Generate Overlap Event para el Mesh en true.

 

Listo !! esto es todo lo que necesitamos para detectar la colisión. Vamos ahora a implementar la lógica de lo que pasa en ese momento.

Causando daño al disparar el evento Overlap entre uno de los dos PunchComponents del personaje principal y el Mesh de este otro personaje.

Vamos a intervenir el evento Overlap e implementar lo necesario para causar daño. Desde el BlueEnemyBlueprint en el modo Components selecciona el Mesh y en el panel de detalles muévete hasta la sección Events. Fíjate que esta sección tiene un combobox que dice Add Event, si lo despliegas se listan todos los eventos de este componente que podemos usar desde código.

Agregando el evento OnComponentBeginOverlap al EventGraph desde el modo Components

Agregando el evento OnComponentBeginOverlap al EventGraph desde el modo Components

 

Da clic en Add OnComponentBeginOverlap. Esto agregará el nodo OnComponentBeginOverlap (Mesh) al EventGraph. Ahora modifica el EventGraph para que te quede de la siguiente forma:

EventGraph del BlueEnemyBlueprint donde se aplica daño al personaje cuando se dispare el método Begin Overlap en el Mesh.

EventGraph del BlueEnemyBlueprint donde se aplica daño al personaje cuando se dispare el método Begin Overlap en el Mesh.

 

Aquí viene lo interesante. Cuando se dispara el método BeginOverlap en el Mesh se usa el nodo Apply Damage para aplicarle un daño a este personaje. Este nodo es súper útil, espera como parámetro cual será el actor al que se le aplicará el daño. En este caso es este mismo actor, por lo que usamos la variable self. Además, permite definirle un valor numérico de daño, en este caso le pasamos directamente 20, pero pudiéramos usar una variable para darle el valor dinámicamente. Los otros tres parámetros son opcionales y pueden resultar muy útiles. Event Instigator se usa para definir el Controller que causa el daño. Damage Causer se usa para definir el actor que causa el daño, por ejemplo, en nuestro caso pudiéramos pasar como parámetro una referencia de nuestro personaje y por último, Damage Type Class nos permite definir una clase con propiedades específicas para extender la información del daño aplicado.

Es importante en este punto aclarar que para poder usar el método Apply Damage en un Actor este tiene que tener la propiedad Can be Damaged en true. Por defecto el character la tiene en true, puedes verlo en el Modo Defaults en la sección Actor.

Por último, para probar lo que hemos hecho, agregamos un nodo Print String que nos permite imprimir el texto PUNCH ! en la pantalla.

Listo !! compila, guarda y ejecuta el juego. Acércate al otro personaje y lanza un puñetazo con la tecla R.

Captura del juego en ejecución en el preciso momento donde se lanza el evento OnComponentBeginOverlap al colisionar el PunchRightComponent con el Mesh del otro personaje.

Captura del juego en ejecución en el preciso momento donde se lanza el evento OnComponentBeginOverlap al colisionar el PunchRightComponent con el Mesh del otro personaje.

 

Perfecto !!, ya tenemos el momento en donde colisiona el puño con el Mesh del otro personaje y en ese punto aplicamos un daño. . . pues bien, una de las ventajas que tenemos al usar el método Apply Damage es que al aplicar un daño se lanza un evento que podemos intervenir para implementar toda la lógica cuando un personaje recibe el daño.

Vamos a intervenir este evento en el blueprint e implementar todo lo necesario para restar la salud del personaje según el daño que recibió hasta que su salud llegue a cero y muera. Pero antes de eso nos falta una cosa. Queremos que cuando el personaje reciba el golpe se reproduzca un efecto de sonido y cuando muera se reproduzca otro efecto. Vamos a usar este pretexto para tener nuestro primer acercamiento al trabajo con sonidos en Unreal Engine 4.

Introducción al trabajo con efectos de sonido en Unreal Engine 4

La música y los efectos de sonidos pueden marcar la diferencia y ser los responsables de que te quedes “como bobo“ delante de un juego. Me pasó con el Rayman Legends y hace poco con el Valiant Hearts: The Great War al escuchar su música. Por cierto, dos juegos que por nada del mundo te puedes perder :)

En Unreal Engine 4 todo lo referente al trabajo con audio se maneja dentro de unos objetos llamados Sounds Cues. Un Sound Cue se puede ver como un blueprint orientado a audio. O sea, siguiendo la misma filosofía del blueprint, del trabajo con nodos, la asociación de un nodo a otro etc, se pueden crear complejos efectos de sonido, mescla entre efectos y muchas cosas más relacionadas con el audio para nuestro juego. Al final este Sound Cue lo podemos tratar como un efecto por si solo.

Vamos a ver un simple ejemplo del trabajo con efectos de audio en UE4. Importa desde el Content Browser estos tres archivos. Son tres efectos cualesquiera para reproducir cuando el BlueEnemy reciba los golpes, puedes usar unos tuyos si los tienes a mano.

En estos momento Unreal Engine 4 solo permite importar archivos de sonido WAV de 16bits con especificaciones PCM, ADPCM, DVI ADPCM y cualquier sample rate, aunque recomiendan 44100 Hz o 22050 Hz.

Desde el Content Browser selecciona el botón Import e importa estos tres ficheros. Verás que se te muestran como Sound Wave. Puedes reproducir cada uno dando clic derecho sobre él y seleccionando la opción Play en el menú que se despliega. Desde el código se pueden reproducir estos Sound Wave directamente sin problema, pero en muchos casos queremos procesar el efecto antes de reproducirlo directamente y para esto es que usamos los Sound Cue. Vamos a ver ambos casos: Crearemos un Sound Cue para hacer que se reproduzca aleatoriamente uno de estos efectos cada vez que el personaje reciba un golpe y cuando muera reproduciremos directamente el tercero, así mismo como Sound Wave.

Para crear un Sound Cue vasta con dar clic derecho en el Content Browser y seleccionar Sounds/Sound Cue. Esto te crea un nuevo ítem de tipo Sound Cue en los recursos del proyecto y te abre directamente el Sound Cue Editor. Por defecto el Sound Cue tiene un nodo Output que representa el resultado final de todo el pre-procesamiento que se haga, como el Final Animation Pose para el caso de las animaciones. Desde aquí, solamente con nodos, podrás crear complejos efectos de sonidos, para que tengas una idea, da clic derecho en una zona en blanco y lee todas las opciones de nodos que puedes crear. Tomate un tiempo y date un recorrido general por el editor para que lo conozcas un poco. Honestamente, de este editor yo solo se lo súper básico ya que no es mi campo, pero estoy seguro que un profesional en el tema lo exprime por completo :)

Bueno, a lo nuestro, selecciona primero del Content Browser dos de los efectos, recuerda que puedes usar la tecla Ctrl para selección múltiple. Con los dos Sound Waves seleccionados abre el Sound Cue, da clic derecho en una zona en blanco y selecciona de la sección From Selected, la opción Random: Multiple WAVs y conecta la salida del nodo Random a la entrada del Output. Te quedará de la siguiente forma:

Sound Cue para reproducir aleatoriamente uno de los dos efectos de sonido.

Sound Cue para reproducir aleatoriamente uno de los dos efectos de sonido.

Con esto acabamos de crear un Sound Cue que podemos manejar como un efecto de sonido normal, pero al decirle que se reproduzca, él sólo se encargará de seleccionar aleatoriamente uno de estos dos efectos y reproducirlo. Súper verdad !! ?

Pues ya con esto estamos listo para retomar la implementación de lo que pasa cuando el personaje recibe el daño.

Interviniendo el evento Any Damage para implementar lo necesario cuando el BlueEnemy recibe el daño por un puñetazo.

Muévete a una zona en blanco del BlueEnemyBlueprint e implementa el siguiente algoritmo

Algoritmo cuando el BlueEnemy recibe daño.

Algoritmo cuando el BlueEnemy recibe daño.

 

Muy bien, vamos con detenimiento por todo este algoritmo porque en él usamos varios nodos que no habíamos usado antes. Primero agregamos el Nodo Event Any Damage. Este evento se dispara cuando este actor recibe daño. En este caso se dispararía cuando se detecta la colisión con el puño del personaje y se llama al Apply damage.

Fíjate que desde este evento podemos obtener el valor de daño aplicado, recuerda que en nuestro caso es 20, además podemos obtener el Damage Type, el Instigate By y el Damage Causer que como vimos, se pueden pasar como parámetros al Apply Damage.

Lo primero que hacemos es restarle la cantidad de daño aplicado a la variable Health que definimos para este personaje para representar su salud y que inicialmente está en 100. El nodo Clamp nos permite limitar el valor entre dos extremos. En este caso 0 y 100, para evitar que al finalizar la operación la variable tome valor menor que 0 o mayor que 100 en caso que fuera posible.

Después de actualizar el valor de la variable Health, usamos un Print String para ayudarnos a ver lo que pasa en cada momento. Usamos un nodo muy útil para el trabajo con strings, el nodo Append, que nos permite unir dos strings. En este caso unimos los strings “Blue Enemy Health” y el valor de la variable Health, fíjate que si intentas conectar la variable Health al puerto B del Append, como son dos variables de tipo de dato distinto, el editor automáticamente nos genera un nodo por el medio que convierte el tipo de dato int de la variable Health a string para poderlo usar con Append.

Después de eso tenemos una condición usando el nodo Branch. Preguntamos si la variable Health llegó a cero. Si es false, es que al personaje aún le queda salud y en este caso reproducimos una animación simple para reflejar el golpe. Fíjate que aquí usamos otra ventaja de los Montage. Recuerdas que en el HitAnimMontage que definimos tenemos tres secciones con tres animaciones de hit distintas que se llaman HitReact1, HitReact2 y HitReact3, verdad ? Pues aquí con el nodo Random Integer in Rage generamos un número aleatorio entre 1 y 3 creamos un string mediante el Append uniendo el string ”HitReact” con el número generado, y de esta forma obtenemos aleatoriamente el nombre de una de las secciones definidas en el AnimMontage. Por último usamos el nodo Play Anim Montage para reproducir la animación y le pasamos por el parámetro Start Section name el string generado. De esta “curiosa“ forma y con la ventaja que nos brindan los AnimMontage, cada vez que el personaje reciba un golpe tendrá una animación aleatoria para reaccionar al golpe

Por último reproducimos el Sound Cue que creamos hace unos minutos y que vimos que sería aleatoriamente uno de los dos efectos de dolor. Para esto usamos el nodo Play Sound at Location. Este nodo espera dos parámetros. El parámetro Sound que es el archivo de sonido a reproducir puede ser directamente aun Sound Wave o un Sound Cue, y el segundo parámetro nos permite definir la posición en el mundo 3D en la que se reproducirá el sonido. En este caso le pasamos la posición de este personaje.

La otra rama del Branch es cuando la variable Health llega a cero, que significa que ese último golpe mató al personaje. Pues bien, para este caso lo primero que hacemos es reproducir una animación de muerte. Pero fíjate que usamos un nuevo nodo para reproducir una animación. El nodo Play Animation nos permite reproducir un AnimSequence directamente sin tener que hacer uso del AnimationBlueprint o de los AnimMontage. El parámetro Looping tiene que estar en false, ya que queremos que esta animación se reproduzca una sola vez y quede ahí en el último frame.

También reproducimos un efecto de sonido, en este caso un Sound Wave directo, solo a modo de demostración.

Por último, hay un detalle. Si pruebas en este momento verás que todo va de maravillas pero cuando tumbas al otro personaje con el último golpe, este caerá al suelo con su animación, pero se quedarán en el medio del camino los dos componentes de colisión, la capsula y la caja. Para solucionar esto, seguido a la reproducción del efecto de sonido usamos el nodo Destroy Component que nos permite destruir un componente determinado y le pasamos el Capsule Component y el BoxComponent. Otra solución puede ser desactivarle la colisión, en vez de destruir por completo el componente.

Seguidamente, usamos el nodo Delay para demorar el algoritmo 3 segundos y por último destruimos completamente el actor de la escena. Esto provocará que después de tumbar al personaje pasaran tres segundos y su cuerpo desaparecerá del nivel.

Listo !!, compila, guarda y ejecuta el juego. Muévete hasta donde está el otro personaje y comienza a golpearlo. Verás que cada vez que le damos un puñetazo lanza una de las animaciones de impacto y cuando su salud llega a cero termina con la animación de muerte. Puedes ver con la ayuda de los Print String como disminuye la salud del otro personaje de 20 en 20 ….

Si, si, si !! . . . se que ahora mismo debes estar fijándote en que las animaciones cuando el personaje recibe un golpe están terribles, con la reacción del personaje parece más a que le están haciendo cosquillas :), pero son las animaciones que tenemos a mano :( . . . ya en nuestro juego, que el equipo de animación nos haga algo mejor :)

Captura del juego cuando el personaje le da el último puñetazo al enemigo y lo derriba.

Captura del juego cuando el personaje le da el último puñetazo al enemigo y lo derriba.

 

Conclusión

Vamos terminando aquí este tutorial, espero que te hayas divertido dándole puñetazos al BlueEnemy :). Si eres de los que prefiere el trabajo con C++ (como yo) un buen ejercicio es que intentes implementar todo lo que hemos hecho en este tutorial pero desde C++, eso te ayudará a alcanzar más soltura con el framework de clases del Engine.

En el próximo tutorial vamos a comenzar a darle armamento a nuestro personaje y ha poner más acción en nuestro juego. Puedes estar al tanto siguiéndome en Twitter (@nan2cc) . . . mientras, me encantaría escuchar tus comentarios y si tienes algún tema específico del que quisieras un tutorial también déjame un comentario, haré todo lo posible por complacerte 😉 . . . hasta la próxima, bye !!

Getting Started with Game Development using Unreal Engine 4

In my humble opinion, Unreal Engine 4 is one of the most powerful game engines that exist right now, and the Epic Games team has released a license for all user, for just 19 USD a month.

This first tutorial gives an introduction to UE4. We will create the basis for our game by having a character walk through a level. We will use a fixed camera and basic game controls. This simple start will allow us to:

– Learn how to import 3D models to the project.
– Create the necessary classes to control the character.
– Understand the philosophy followed by Unreal’s framework in its classes model.
– Give an introduction to programming in Unreal Engine using C++ language.
– Understand the communication between the C++ classes and the Unreal Editor.
– Know the animation mechanism of the character.
– Introduce Visual Scripting using the Blueprint Editor.

Getting Unreal Engine 4

Getting the game engine is simple. First, you need to register at https://www.unrealengine.com/register and pay the 19 USD. Trust me, it will probably be the best 19 USD you ever spend. After you’ve paid and registered, you’ll have access to the same tools that the Epic Games team work with.

The next step is to actually get the engine. There are two ways to do this. The first way is launching the Launcher application. This application will allow us to download the latest version of the engine. The other way is to build the engine from the source code. That’s right, we have access to all the source code of the engine.

The official website gives a good explanation on the steps needed to download and build the source code from https://github.com/EpicGames/UnrealEngine/releases We’ll leave this topic here. If you have any problems with this process, please leave me a comment.

Model and Animations of the Main 3D Character

First thing we need to start our game is the main 3D character’s model and its animations. All of the 3D models that are used in a game, including the characters, objects, environments and animations, are created by the designers and animators of our team. We use modeling and animation 3D tools like 3DsMax or Blender. When the modeling and animation processes are over, the resulting model is exported with its skeleton and animations in the FBX format.

The 3D modeling and animation isn’t my strongest suit☺. Let’s start with the FBX resources from one of the sample projects released with UE4. This is exactly what our designer’s team will give us. You can download the resource from: https://d26ilriwvtzlb.cloudfront.net/a/a7/ThirdPerson_FBX.zip

Extract the contents of the .zip file. For now we will only work with the HeroTPP.FBX, Walk.FBX and Idle.FBX files. If you have any 3D modeling software, such as Maya 3D, you can import these files into the software, interact with them and examine them.

HeroTPP.FBX file loaded in Maya 2015. Skeleton view.

HeroTPP.FBX file loaded in Maya 2015. Skeleton view.

HeroTPP.FBX file loaded in Maya 2015. Model view.

HeroTPP.FBX file loaded in Maya 2015. Model view.

 

Hero.FBX is the 3D model of our main character along with its skeleton that will allow us to animate the model. Idle.FBX and Walk.FBX are, as the name suggests, the idle and walk animations. These last two files don’t contain any model because a model isn’t necessary in animation files. Animation files only hold information about the skeleton’s movement. That’s why there’s no need to export the 3D model when we export the animations.

Creating a New Project in Unreal Engine 4

Now that we are ready to begin, let’s start by creating the project. This is a very simple task in UE. We need to open the editor. The editor will show a window with two tabs: the Project tab, that contains all the previously created projects, and the New Project tab, that as its name suggests, allow us to create a new project.

A cool feature when creating a new project is a project group called Templates that we can use as base for our game. Using this, we can select the type of game that we want to develop. The available options are: 3rd Person, 1st Person, Top-Down or Side-Scroller.

Our goal in this first tutorial is only to offer an introduction to UE4. We aren’t going to use any of these relatively advanced templates. Instead, we’re going to create our project practically from scratch. We will select the Basic Template option. Select New Project/Basic Code. In the text field below type in the name of your project. In my case, I will use UE4Demo. Finally, we click the Create Project button.

Window to create and open a Project in Unreal Engine 4.

Window to create and open a Project in Unreal Engine 4.

This will create the project and will automatically open the IDE according to the operating system we’re using, Visual Studio 2013 in Windows case and XCode 5.1 in MAC OS. This tutorial is being developed in MAC OS so I’m using XCode as my IDE. Once the IDE has opened the project, we have to compile the project so we can open the Editor. Click in the upper-left corner to select “Scheme UE4DemoEditor-Mac” and then the “Product/Build For/Running” menu option.

The building process might take a while. Once it’s over we can select the “Product/Run” XCode menu option. This will prompt the Editor with our project opened.

Unreal Engine 4 Editor with our new Project just opened.

Unreal Engine 4 Editor with our new Project just opened.

UE4 provides us a scene with some objects added to it. For now, let’s leave the scene this way. You can select the Play button from toolbar to see all of the objects that compose our current scene. By default, we the control the camera with the mouse and keyboard. We could move through the whole scene, but this isn’t our goal. We will like to add our character to this world :)

Importing our character 3D model.

We already have our 3D models exported as FBX files with its skeleton and its two basic animations☺. Now let’s import them into our Project. In the left-bottom corner panel of the Editor we have the Content Browser. This panel is where we will have all of our game resources. Select the New Folder option and give it a name to the folder. For instance, “Character”. After this operation we have a new folder in the content browser. Go into this folder, select “Import” and search the FBX file of the character: HeroTPP.FBX (we will be importing the FBX files of the animation later on). Remember that this FBX file contains the 3D model and its skeleton. Select “OK” and the FBX “Import” window will appear. The Skeletal Mesh associated with the file will automatically be selected.

FBX Import window of Unreal Engine 4

FBX Import Window of Unreal Engine 4

Let’s take a break and theorize a little. As you can see in this import window, there are three types of resources that could be imported from a FBX file: a Static Mesh, a Skeletal Mesh and an Animation.

Static Mesh: A static object of our game. For instance, a chair, a building, the environment are static meshes. In other words, a Static Mesh is a 3D model without an animations or a skeleton.

Skeletal Mesh: Exactly the opposite of a Static Mesh and also the type of model that we’re importing. A Skeleton Mesh is a 3D model with an associated skeleton that can be animated. So to be clear, all the characters of our game will have a Skeletal Mesh.

Animation: Animations contain the transformation information of each of the bones of a skeleton to bring it to life when performing actions such as walking, jumping, etc. Examples of this are the Idle.FBX and Walk.FBX that we will soon import.

UE4 automatically detects that we’re importing a Skeletal Mesh so isn’t necessary to change anything. The default parameters are suitable to our purpose. The only thing left to do is to click the Import button. Ignore any warnings received during the import process. In the following tutorials, we’ll see the whole Export/Import process and the Animation Rigging Toolset provided to us by Epic Games to prepare the animations and models.

Once the model is imported in the Content Browser, we’ll have three new elements: Hero (SkeletalMesh), Hero_PhysicsAsset (PhysicsAsset) and Hero_Skeleton (Skeleton).

If you double click in the SkeletalMesh, you can open the imported model in the Persona Editor of UE4. Persona is the Skeleton, Skeletal Meshes, Animations Blueprints and other animations elements Editor in UE4.

The Skeletal Mesh of our character in the Persona Editor

The Skeletal Mesh of our character in the Persona Editor

When opening the Skeletal Mesh in the Persona Editor, we have on the left side the Skeleton Tree. This skeleton tree is composed of all of the bones that make up the skeleton’s model. At the bottom, we have the Mesh Details Panel. This is composed of several sections that describe the materials applied to the selected model. At the moment, we don’t have any material. Or rather, we only have the default material that gives an opaque view to our character.

The Hero_PhysicsAsset is the PhysicsAsset generated automatically during the process of importing the Skeletal Mesh. For now, we won’t use this asset. In future tutorials, we will see what it is used for, but if you’re a curious person double click on it, it will open the UE4 specific editor for this kind of assets. In the left-upper corner, there is a Simulate button. Click on the button and see what happens. This will give you an idea of the goal of this resource, generated automatically when importing the Skeletal Mesh.

Finally, the Hero_Skeleton is the model’s skeleton that we’ve just imported. A brilliant feature of UE4 is that we’re able to share the same skeleton between different 3D models that look relatively similar. So instead of importing the 3D model and its skeleton for each of these similar looking models, we will import the skeleton only once and then we will associate the skeleton with the different models.

So now that we’ve all the resources associated with the main character in our project, we’re able to start the main part — the coding :).

Introduction to Programming in Unreal Engine 4

A project in UE4 is basically composed of two big pieces that work together. The levels, which are developed on the Editor, and the programming project that is developed in our programming IDE. We already saw how when we created a project in UE4, the engine creates both of these pieces. We will now focus on the second part, the code.

The programming language used in UE4 is C++. When creating a new UE4 project, a C++ is automatically generated. This C++ project can be edited in XCode if you use MAC OS or Visual Studio if you use Windows. This project already contains the basic classes of our game. Open your IDE and select the project just created. The source code for our game, UE4Demo, is contained in the Source files. All of the UE4 framework source code is contained in the Engine files. The possibility of having access to this framework code is amazing because we are able to inspect the implementation of the framework classes or what is used for a certain property. This code is commented by Epic Games Team. Before we create our first class, we will quickly discuss some comments about the philosophy followed by UE4 in its framework.

In UE4, all the elements that appear in our game are Actors (in other words they inherit of the AActor class). For example, a chair, table, enemy or even the main character are all actors. The game elements that are controlled, non-static, and have a behavior are known as Pawns. There is a special kind of Pawn called Character. This is the Pawn that represents the main character and contains the particular implementations designed only for the model controlled by the gamer. For instance, if in our game we have a main character and an enemy, the first one will be a character and the second one will be a Pawn. All Pawns are controlled by a class called Controller. The Character is controlled by the PlayerController class. The PlayerController class receives input from the game and uses data to control the character in the game. Basically, the PlayerController object represents the human actions in the game. All non-player characters pawns are controlled by the AIController class.

It’s a little bit tricky at first, but bit by bit you will become familiarized with UE4 and come to understand its philosophy, class hierarchy, and their relations.

In the source code for our game, we have a file with the same name as our project. In my case, this file is called UE4Demo. Inside this file are a few classes that we can start working with.

The first class we are going to review is UE4DemoGameMode. This class defines our game’s Game Mode. The Game Mode describes the game’s rules, such as the win and lose conditions. The Game Mode also has the responsibility of defining the PlayerController and the default Pawn, among other things. UE4DemoGameMode is the core of the game. If we open the .h file, we will notice that this class inherits from AGameMode and for now doesn’t contain anything else.

//AUE4DemoGameMode.h
#pragma once

#include "GameFramework/GameMode.h"
#include "UE4DemoGameMode.generated.h"

UCLASS()
class AUE4DemoGameMode : public AGameMode
{
    GENERATED_UCLASS_BODY()
};

The class declaration contains two macros that should have caught your attention: UCLASS() and GENERATED_UCLASS_BODY.

UE4 has a strong system for the object handling. The base class for the objects in Unreal is UObject and the UCLASS macro should be used in classes that inherit from UObject. This informs the UObjects system handler of the existence of the new class.

When including these macros in our classes, we ensure that they have access to some of Unreal’s low level mechanisms, such as garbage collection, serialization, automatic initialization of properties, automatic editor integration, and more.

Now let’s see our GameMode implementation. As you can see, in UE4DemoGameMode.cpp, only the constructor is implemented.


//AUE4DemoGameMode.cpp
#include "UE4Demo.h"
#include "UE4DemoGameMode.h"
#include "UE4DemoPlayerController.h"

AUE4DemoGameMode::AUE4DemoGameMode(const class FPostConstructInitializeProperties& PCIP)
    : Super(PCIP)
{
    PlayerControllerClass = AUE4DemoPlayerController::StaticClass();
}

For now, the constructor only contains the initialization of the PlayerControllerClass attribute. This PlayerController for our game will act as an interface between the user and the character. In here, we initialize the PlayerControllerClass with a static instance of our own UE4DemoPlayerController. That’s why we can move like a ghost through the whole level when we run the project.

We already have a PlayerController but so far we don’t have a Character that represents the physical body of the character. The UE4DemoPlayerController class previously created is the PlayerController implementation for our game. This is empty at the moment. For now, we don’t need to personalize it. All that we need to control our character is already in the APlayerController class. We’re going to leave this class for now but can come back later when we need to add some custom behavior.

That’s enough theory for now. Let’s code!

We’ve already imported all the resources for our character in the Editor, so now let’s bring it to the scene :)

Creating our First Class in UE4

The first thing we’re going to do is to give life to our character, so we will create our character class. As I said earlier, the class in charge of controlling the character is the Pawn class. This is a special kind of Pawn, a Character, that’s why our class has to inherit from it.

The easiest way to add a new class to the project is through the Editor. Go back to the Editor. In the main menu, select “File/Add Code to Project”. Now a window will prompt to select the base class. As we already said we will use Character as our base class. In this window, you can inspect the most common UE4 classes and read a brief description on each of them. So select “Character” and hit the next button. Type in the name of your class. I will use HeroCharacter. Finally, end the class creation process. When the process completed, you will be asked if you want to open the IDE. Select OK and when the IDE opens, our newly created class will be there. The class structure was already described earlier. A class that inherits from ACharacter includes the UCLASS and GENERATED_UCLASS_BODY() macros that import the low level mechanisms (garbage collection etc.) from UE4.

Configuring the Character from the Blueprint Editor

We’ve configure our main character so this is a good time to make a personal comment about one of the mechanisms of UE4 that was very difficult for me to get used to because I came from a background of 2D game development. In game engines like Cocos2d, the majority of game logic and building is done using code. In UE4, the work philosophy is very different. Here we have two options for making our game. The first option is to do things by hand coding. This can cause us to have a slower rhythm in the production of code and is susceptible to bugs. The second option is by using the Editor. The downside of this option is that this new method of programming for can difficult for us programmers to adjust to. In this tutorial, we will work in the Editor or by code in the IDE. We leave the decision to you to choose which method to use to perform different tasks.

To demonstrate the UE4 integration between the code and the Editor, we will configure our character in both ways. This will let us show how amazing the communication between the C++ code and the editor is.

Now that we have our own class to represent the Character, let’s configure its components from the Editor. The first step is to open the Editor. In the Toolbar, we have the Blueprints button. Go ahead and select Blueprints/New Class Blueprint. In the bottom of the prompted window is a section named Custom classes. In that section, search and select the class we’ve just created for our Character, HeroCharacter. Name it, for instance as HeroCharacterBlueprint, and select the folder Game/Character to store the file. Once the process has finished, the Blueprint Editor will open up in the Components mode where we will configure our Character.

imagen_06

In the left side of the editor, we have the Components panel. As its name suggests, this panel contains all the components that compose the Character. The bottom panel shows the properties of the selected component. The CharacterMovements is the component that handles the movement of the Character. For example, here we hold the Max Walk Speed that is the top speed with which our character will be able to move. There are many more properties, and I recommend you to take a quick look over some of these properties to have an overview of all all the things that are configurable regarding movement.

The other component that the Character has is the CapsuleComponent. The CapsuleComponent is in charge of the collision detection. In the Viewport of the Editor, you can see it graphically as a transparent capsule, which represents the collision zone of the character.

At last, inside the CapsuleComponent we have a Mesh and I’m sure you already figured it out that is the Mesh that represents our character. There is also an ArrowComponent which informs us of the direction of the Character.

The only thing left is to finish the configuration of the Mesh. We must select the mesh component in the Component panel and the skeletal mesh property in the Details panel in the Mesh section. Select the dropdown and select the Skeletal Mesh of our character, the one we created when our hero FBX file was imported. We will see our hero model in the viewport. Use the translation and rotation tools to place the Mesh inside the CapsuleComponent and match the direction to the ArrowComponent. Finally, click the Save button in the upper-right corner of the Editor, and now we’ve given a body to our character. Let’s try it out. Close the Editor and run the game to see what we’ve so far.

Character’s Mesh in the right position in the HeroCharacterBlueprint

Character’s Mesh in the right position in the HeroCharacterBlueprint

:( As you noticed there are no changes. We still have game controls thanks to the default PlayerController, but we don’t have yet our character. The problem is that there are some things that we still have to setup.

First, make sure you have properly configured the GameMode. Click in the World Settings button of the Toolbar and make sure that you selected our UE4DemoGameMode class in the GameMode section. Under GameMode are all the elements configured for our game mode. For instance, in the PlayerControllerClass section, we have our PlayerController (UE4DemoPlayerController). Due to that in the UE4DemoGameMode constructor we initialize this property, but Pawn Class has the Default Pawn value, so we’re not over yet. The problem is that we created our character and gave it a body but we haven’t defined in the GameMode/Pawn Class that our HeroCharacter class is our character. Let’s do this by code, once again to demonstrate the amazing communication between the Editor and the code. So next we’re going to close the Editor and open the C++ project, search the UE4DemoGameMode class and modify the constructor implementation to match the below code:

AUE4DemoGameMode::AUE4DemoGameMode(const class FPostConstructInitializeProperties& PCIP)
    : Super(PCIP)
{
    PlayerControllerClass = AUE4DemoPlayerController::StaticClass();
    
    //Gets in PlayerPawnBPClass.Object the HeroCharacterBlueprint class created and configure from the Editor
    static ConstructorHelpers::FObjectFinder<UClass> PlayerPawnBPClass(TEXT("Class'/Game/Character/HeroCharacterBlueprint.HeroCharacterBlueprint_C'"));
    
    if (PlayerPawnBPClass.Object != NULL)
    {
        DefaultPawnClass = PlayerPawnBPClass.Object;
    }
}

Let’s take a minute to explain step by step what we just did cause even if you are an experienced C++ developer the previous syntax could be tricky. In the first line, we search and get the instance of HeroCharacter created in the Editor through the Blueprint Editor. Next we create a variable of FObjectFinder type, this is a public parameterized structure that holds inside another structure this one named ConstructerHelpers. FObjectFinder receives in its constructor the address of the object that we will instantiate, if the object is found successfully its instance it’s stored in the Object property.

It might be useful to seize the opportunity to review the framework source code. You can see the implementation of all structures, such as ConstructorHelpers, FObjectFinder, etc. In XCode, all you need to do is click the name while pressing the cmd key and you’ll be taken to the structure declaration. So take a minute to familiarize with these new structures in order to understand how they work. Also, you must note that to define the route to FObjectFinder we use the TEXT macro. This macro is used for all strings in the engine in order for the compiler to convert the string to the right data type, which in this case is a TCHAR*.

So now we have in PlayerPawnBPClass.Object the instance of our Character (PlayerPawnBPClass is the name that we gave to the variable that we created of FObjectFinder type). The only thing left is to initialize the DefaultPawnClass with this object. The GameMode class has the DefaultPawnClass attribute. This represents the Pawn that our character will use and we only have to set our class.

Now we are ready to play. Compile and execute the game. When the game starts our character is automatically added to the level. This is the level created by default for the selected template in the project creation. The level has an Actor of type Player Start. When the game starts, it searches for an instance of Player Start and adds our character at this position. The bad news is that now we lose the game controls so we are unable to move through the scene and the camera point of view is now the character’s eyes :( Temporarily, we solve this by setting a static camera in our game.

Setting a static camera by code.

Right click inside the viewport in the editor and select the folding menu Place Actor/Camera. Use the translation tools and transformations to aim the camera in the Play Start direction so we will be able to see the character. This is how I did it:

imagen_08

Now let’s inform Unreal that the game view will be taken from this camera. For this we have to come back once again to the theory.

We already discussed how according to Unreal philosophy, PlayerController is the interface between the main character of the game and the human being. So it’s completely natural that the logic regarding the game view will be implemented in this class. PlayerController has the SetViewTargetWithBlend method. This method allows us to control the camera point of view. We will call this method to set the camera we created as the game’s camera. We can change the camera’s point of view any time we want to. In this case, we will configure it to the direction of the camera that we created. In order to do that I will introduce you to one of the most used events in Unreal, the BeginPlay event. All classes that inherit from AActor have this method which is called, as its name suggests, when the game is about to start. Let’s add this method to our PlayerController class (that inherits from AActor) and change the game camera.

Open UE4DemoPlayerController.h file and add the next line: virtual void BeginPlay() override; below the GENERATED_UCLASS_BODY() macro, with this declaration we’re able to override the base class method and define our customized behavior. Go on to the .cpp file to define the method. Add the below code under the class constructor. I recommend you thoroughly review the comments so you can understand what we are doing in each step.

/** Inherit method of the AActor class it’s called automatically by the engine when the game starts. */
void AUE4DemoPlayerController::BeginPlay()
{
    //Call to the Begin Play method in the base class.
    Super::BeginPlay();
    
    //We iterate among all the actors in the level through the TActorIterator
    //TActorIterator is a parametrized iterator that allow us to iterate among all actors in the level.
    for (TActorIterator<ACameraActor> It(GetWorld()); It; ++It)
    {
        //We get the current actor in the loop. As we only have an ACameraActor in the level, the iterator will iterate only once.
        ACameraActor* _mainCamera = *It;
        
        //We configure the new point of view of the camera as our game view.
        //SetViewTargetWithBlend can receive more parameters, but the method has default values and in this case we won’t need more specialization so we’ll leave like this.
        this->SetViewTargetWithBlend(_mainCamera);
    }
}

We’re ready. Compile and run the game. Now you can see the level and our character viewed from the camera that we created.

Setting up a static camera through the Blueprint Editor.

I’ll take a break to talk about the Blueprint Editor. I’m sure that when you start in UE4 you asked yourself the same question that I did when I started – how do I do this? Through the Blueprint Editor or coding it in C++? The answer is a question of taste. As you advance in UE4 you will know which one is the best option for each task.

In order to compare both methods the next thing we will do is to setup the camera like we just did by code using the Blueprint Editor without typing a single line of code. Keep in mind that not all tasks are the same, so maybe the one that you find simplest now won’t be the best option in another case. In the Editor’s toolbar, click the Blueprint button and select the Open Level Blueprint option. This will open a blueprint file that concerns all of the logic for the whole level.

Following the logic that we use in C++, the first thing to do is to implement the BeginPlay event. The Blueprint Editor is a visual scripting editor, so basically we’re still programming but graphically (yes I know this sounds a little crazy at first☺). In here, we can add new variables, system events, specific functions classes, etc., Basically, you can do nearly everything that you can do in C++.

To add the BeginPlay event, right click in the center of the screen and uncheck the Context Sensitive option and search the Begin Play event. We’ve added a node that represents the event to our visual script. Next we will get the reference to the level’s camera and call the SetViewTargetWithBlend method in the PlayerController class passing the camera as parameter. To get a reference to the PlayerController, remember this is a “global” script in the “Level” level and the C++ implementation was made inside the PlayerController. Add a new node of the Get Player Controller type. This node returns the reference to the PlayerController of the game. Now we need to call the SetViewTargetWithBlend. Again we add a new node with the method’s name and now we have all the elements that we need to complete our visual algorithm. There is only one thing left, connect all the nodes.

Our first node (BeginPlay) has a port that represents the output, in other words, the execution of the call to this method. The SetViewTargetWithBlend node has both an input and an output ports. Click the first node port and drag the arrow to the input port of the method node. Release it when a green mark appears. Now when the game starts, the BeginPlay event will be executed and this will call the SetViewTargetWithBlend method. This method belongs to the PlayerController class so we need to define which is its PlayerController. The Get PlayerController node has an output port titled Return value we will connect this port with the SetViewTargetWithBlend port named Target. In doing that, we are defining the reference to the method. Remember that we also have to pass another parameter, the Actor, which we will use to configure the camera point of view. Finally, we need to add the camera node. Save these changes. Close the editor and select in the level the camera was previously added. Now open the editor once again, right click and you will notice that you have a shortcut to add a Camera Actor node. Once added, connect the output port of the camera actor to the New View Target port of the SetViewTargetWithBlend. Now we are done, our visual script is complete. Click the upper-left corner button Compile.

Before we play the game, let’s delete the override declaration and definition of the Begin Play event in the C++ code. You can comment it out if you want to so you don’t lose the code. Close the editor and comment out the UE4DemoPlayerController.h class in the code. Do the same in the .cpp file.

Compile and run the game. As you’ll notice, we’ve achieved the same result☺. So I repeat once again it’s your call either to choose the Editor or the code. You will have to wait to gain your own experiences and find out about the pros and cons of each method.

I recommend you take a break here because we’ve reached the middle of the tutorial and there are many more things to come :)

Setting the character’s animations

So far we only have our static character viewed from a fixed camera, which isn’t very functional, so let’s give it more life.

Remember that our design team :) also gave us the character’s skeleton and the walk and idle animations ready to be imported. Open the Editor and create a new folder in the Content Browser. I named the folder Animations. Open the folder and import the two FBX files: Walk.FBX and Idle.FBX. There is an option that allows us to select the skeleton associated with the animation being imported in the importation process. Select the Hero_Skeleton and click the Import button to finish the process.

Now you can inspect both files. Double click on one of them in the Content Browser and the Persona Editor will open showing a preview of the animations. From this Editor you can see all the animations details, play it and so much more that well have to cover that in future tutorials. The only thing left is to associate the animations with the character.

Creating the state machine and updating the character with the idle and walk animations using the Animation Blueprints

The Animation Blueprint is the tool that UE4 provide us to implement all the animation logic in a really simple way, using the state machines and visual scripting.

So let’s create the Animation Blueprint for our character. In the Content Browser, go to the Animations folder, right click in an empty space and select Animation/Animation Blueprint. This will open the Animation Blueprint window. In the Parent Class section, select AnimInstance and below Target Skeleton section write the name of the skeleton that uses our hero, Hero_Skeleton, and finish the process by clicking on the OK button.

The engine will automatically add an AnimBlueprint to the Content Browser with its name selected in case that you want to change it. Name it whatever you like. I will name it HeroAnimBlueprint. Before we do anything in the blueprint, let’s define that our character will use this blueprint to control its animation. To do this, search in the Content Browser where your character’s blueprint is, in my case is in: Game/Character/HeroCharacterBlueprint, and double click it. In the upper-right corner select the Defaults mode and you will see a section named Animation. In the Animation Mode property, select Use Animation Blueprint and in the Anim Blueprint Generated Class, select the class that we create HeroAnimBlueprint_C. Now we are ready. Save and close the Editor.

imagen_09

Now that everything is well configured, let’s open the HeroAnimBlueprint. This will open the Persona Editor. Right from the start we’ve a node Final Animation Pose. We will connect this node to the output of the node that we’re about to create, but first, as usual, a little bit of theory.

In UE4, we have an amazing and intuitive mechanism to define which animations to play for characters based on the state of the character. For example, if the character is resting, the idle animation will be played but if the character is walking, the walk animation will be the one played. You can do blending between the animations in order to ease the changes between them. In this case, we need define if the character is in the idle state or the walking state. We’ll do this by adding a “velocity” attribute, and say that if this variable value is 0 then the character is idle so the idle animation must be played, but if the velocity value is anything other than 0, the walk animation should be played. We can do all of this through a state machine and visual scripting in the Animation Blueprint.

Next we create the state machine. Inside the AnimBlueprint Editor, right click and select StateMachine/Add State Machine. This will add a state machine node type to the scene. Change its name to something more descriptive, such as HeroStateMachine, or something else you prefer. Now, inside the state machine we define states that our character will have. Double click on the state machine node to edit it. Inside you will find a node named Entry. Right click, select Add State, and name the created node Idle. Now connect, as we did in the level blueprint, the output port of the Entry node to the input port of the Idle node just created.

Now that all is clear, let’s define the Idle state logic. Double click on the node to edit it, then right click and select Animation/Play Idle. This adds the node that represents the Idle animation that we previously imported into the Editor. Finally, connect this node to the Final Animation Pose node.

Let’s check how all the connections are set in each and every one of the levels. From inside to outside, we have the Play Idle node connected to the Final Pose Animation node. In the upper level (HeroStateMachine), we have the Entry node connected to the idle node. Lastly, in the most upper level (AnimGraph), the HeroStateMachine node connected to the Final Animation Pose. Click the compile button in the upper-right corner of the editor. Notice that in the left panel that you can see a preview of the character with the idle animation.

imagen_10.1

imagen_10.2

imagen_10.3

Run the game. Here is our character in its rest state and with the idle animation.

Main character in the game in rest playing the idle animation.

Main character in the game in rest playing the idle animation.

Setting the game controls

So far, we’ve created and set our character, a temporary fixed camera, and the character’s first animation. The next step, as I’m sure you’ve already guessed, will be to make our character walk :)

First of all, we need to define the game controls. In order to do this, go to UE Editor’s menu Edit/Project Settings, Engine/Input section, Bindings block, click the + button in Axis Mapping and unfold the arrow, in the writing field type MoveForward. Click the arrow aside the EditText and unfold the field and select the W key and set a 1.0 value in the scale. Click again in the + aside the MoveForward’s EditText and select in the new combobox the S key and set -1.0 in the scale.

Now click in the + sign of Axis Mappings to create another section at the same level of MoveForward. In the new EditText, write MoveRight and add two children: A with Scale of -1 and D with 1.

imagen_12

We just defined our game controls. We need to give these game controls names to be able to call this entry from code and the value selected in the combobox is the control that will call this action. The scale value is a numeric value that can be retrieved in the code as a parameter. Generally, this value will be either -1 and 1. Another thing to notice is that the scale value allows us to define actions under the same identifier (MoveForward) and define both directions, given it a positive value of 1 and a negative of -1.

Now let’s code our character logic to move through the scene with this controls. This is fairly simple but enough to understand how it all works out. Basically we will need two things: Override the virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) method of APawn class to register the callbacks that will be called when the entries just created (MoveForward and MoveRight) are pressed. Also, we need to implement the character movement according to the entry pressed.

Open the HeroCharacter.h class and modify it to match the code below:

UCLASS()
class AHeroCharacter : public ACharacter
{
    GENERATED_UCLASS_BODY()
    
protected:
    
    /**
     * Is called when the engine detects the configured entry to 'MoveForward'.
     * In this case when the user press the W or S keys
     */
    void MoveForward(float Value);
    
    /**
     * Is called when the engine detects the configured entry to 'MoveRight'.
     * In this case when the user press the A or D keys
     */
    void MoveRight(float Value);
    
    /**
     * APawn class method that allows to configure the controls binding
     * Is called automatically by the Engine
     */
    virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) OVERRIDE;
    
};

So far, we simply defined two methods where we will implement the logic when one of the keys that we defined is pressed and we also added the SetupPlayerInputComponent method statement to our class in order to override it.

Next, go to the corresponding .cpp file and modify it like this:

#include "UE4Demo.h"
#include "HeroCharacter.h"


AHeroCharacter::AHeroCharacter(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{

}

void AHeroCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    //Informs the engine that when the MoveForward entry is detected call AHeroCharacter::MoveForward method
    InputComponent->BindAxis("MoveForward", this, &AHeroCharacter::MoveForward);
    
    // Informs the engine that when the MoveRight entry is detected call AHeroCharacter:: MoveRight method
    InputComponent->BindAxis("MoveRight", this, &AHeroCharacter::MoveRight);
}


/**
*   It gets called when the MoveForward entry is detected (When the keys W or S are pressed)
*  Calculates the direction of the character and applies it a movement (positive or negative) in that direction.
*
*  @param Value is equal to 1 when W is pressed and -1 when S is.
*/
void AHeroCharacter::MoveForward(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
         //Gets the current rotation
        const FRotator Rotation = Controller->GetControlRotation();
        
         // Creates the direction vector and applies the movement
        const FVector Direction = FRotationMatrix(Rotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
}

/**
*   It gets called when the MoveForward entry is detected (When the keys W or S are pressed)
*  @param Value is equal to 1 when D is detected and to -1 when A is detected.
*/
void AHeroCharacter::MoveRight(float Value)
{
    if ( (Controller != NULL) && (Value != 0.0f) )
    {
        //Determines the direction of the side movements. Notice that we only have interest in the rotation in the Y axis.
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);
        
         // Creates the direction vector and applies the movement
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
        AddMovementInput(Direction, Value);
    }
}

In here, we’ve a couple of things to comment, but I’m sure by now you already have some thoughts about it. In SetupPlayerInputComponent, we use the method’s parameter to set the callbacks that will be called when an entry is detected. In other words, with InputComponent->BindAxis(“MoveForward”, this, &AHeroCharacter::MoveForward); we are saying that when the Engine detects the MoveForward entry (defined as pressing the W and S keys) will call the method that matches the entry name, and the same principle is followed by the other entry. This is because we name the method as the entry, but this isn’t mandatory.

Next we check out the MoveForward implementation. This method receives a float as a parameter. This is the already known scale value registered in the Editor, so if the user press the W key, the method will receive a 1.0 value, and -1.0 if the S key was pressed. This method decides the rotation of the model and the direction vector in which it is oriented, and through the AddMovementInput method we make the character move in the resulting direction. Notice that the character moves upwards or backwards according to the value. AddMovementInput is APawn method that allows us to apply a movement to the Pawn in case that the character isn’t a physical body, like ours. In the MoveRight case, we apply the same logic but all of the calculations are based on the Y axis.

That’s all that we need for now. Compile and run the gamez. When the game starts, press the W, S, A, and D keys to control the character. Be careful not to fall off the edges of the platform ;). Unfortunately, we still have two problems with this movement. First of all, the character doesn’t rotate in the direction of the movement. Second, when the character is moving through the scene, it isn’t playing the walk animation. Let’s fix these two problems. To solve the rotation problem, open HeroCharacter.cpp and modify the constructor so that it looks like the following:

AHeroCharacter::AHeroCharacter(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
    //This property by default has true value for the character
    //But in our displacement model we don’t want that the character rotates based in the controller rotation.
    bUseControllerRotationYaw = false;
    
    // CharacterMovement component configuration
    
     //This way we enable that the character rotates in the movement direction when the movement starts
    CharacterMovement->bOrientRotationToMovement = true;
    
    //Rotation factor to the previous property
    CharacterMovement->RotationRate = FRotator(0.0f, 540.0f, 0.0f);
    
    //We decrease the MaxWalkSpeed default value so the character walks slowly.
    CharacterMovement->MaxWalkSpeed = 400.0f;
}

Do you remember that the character has a CharacterMovement? We modified some values to change the default behavior of the character. I recommend you review the HeroCharacterBlueprint in the Editor to check all the properties that the CharacterMovement contains. Play around with them to see all that can be changed regarding the movement of the character.

Adding walk animation to the character

Now let’s focus on the last problem we have to solve. We need to make the character play the idle animation when he stays in rest, like it is now, but when the character is walking it should play the walk animation. In order to achieve this, we will see one of the two mechanisms that Unreal provide us to ease the transition between animations – the Blend Space.

Blend Space is the Animation Blueprint node that allows us to do blending between animations based in entry values. Unreal provides two types of Blend Space: the Blend Space used for several entries and the Blend Space 1D only for one entry. For this simple example, we will use the second one given that we only need one entry – the displacement velocity.

Once again open the Editor. Inside the Animations folder in the Content Browser, right click and select Animations/BlendSpace1D, name it IdleWalkBlendSpace1D, then double click it to open the respective Editor. We want to define the blending of the two animations using the value of the velocity variable. In the property panel of IdleWalkBlendSpace1D, in the X Axis Label, write Speed (this is only a reference, it could be any other word that represents the value that we have in mind to change from one animation to the other). In the range field, set 0 to 100. Now notice that at the bottom, there is a space with something that seems a graphic axis. From the Asset Browser, drag the idle animation and place it at the start of the x axis. Do the same for the walk animation but place it at the end of the axis. Done, now move the cursor over the axis and notice how in the Preview panel, the animation changes smoothly between the two animations by itself. Save and close the editor.

Setting the IdleWalkBlendSpace1D

Setting the IdleWalkBlendSpace1D

Now let’s modify the HeroAnimBlueprint so we can change the state machine for the behavior of the idle node (if you want to you can rename it to Idle/Walk). Now we will use this node to handle the two state. Enter to edit it, delete the idle node that we’re using and add the IdleWalkBlendSpace1D that we’ve created. This node, unlike the other, has an input port named Speed (the name we used when we created it), so in order for this to work we need to supply a value. There is an icon with a +V from Variable. This will allow us to add a new variable to the graphic. Click it and named it speed, drag it to the workspace and when it asks you Set/Get, select Get. Lastly, connect Speed to IdleWalkBlendSpace1D and this to Final Animation Pose.

imagen_14

We have to define, in the EventGraph, how we will provide the speed value to the idle/walk node. We will implement an algorithm that, in each loop of the animation, will get the Pawn (our character) after the call to the GetVelocity method that returns the displacement vector from the character in a given moment. Once we have the velocity, we calculate the distance of the vector and this will be our speed value. We will do all of this in HeroAnimBlueprint/EventGraph.

We’ve already seen the visual scripting in the Blueprint Editor so this will be a good time to reiterate upon how it works. It’s worth pointing out that we can also do this by code. But in the case, for controlling the logic of the animation behavior, I strongly recommend you to use the Blueprint because the preview tool allows you to easily check the changes and the final result. Also, all the animation mechanisms will be in a single place.

Open the HeroAnimBlueprint/EventGraph, then add and connect the nodes as in the next image. I won’t repeat how to do this step by step because detailed this earlier.

HeroAnimBlueprint/EventGraph algorithm contains the logic to set speed variable value used inside the state machine of the character to change between the idle and walk animations.

HeroAnimBlueprint/EventGraph algorithm contains the logic to set the speed variable value used inside the state machine of the character to change between the idle and walk animations.

Okay, now we’re really done. Compile, save the blueprint and click play to see the game. Pretty cool!!! Right, we have a character smoothly moving through the scene using the walk animation when it’s walking and the idle animation when it’s at rest☺.

Conclusion

We’ve finished this first tutorial: Introduction to Unreal Engine 4. In the next tutorial, we will modify the camera, the game controls and we will restrict the character movement in order to create a side-scroller style game. We will also add some more logic to the game, place some coins in the scene, and have our character collect all the staged coins in a given time in order to win or otherwise lose and start over. Meanwhile, please leave me your comments :). Also, you can follow me in Twitter (@nan2cc) to stay tuned for the next tutorials.

Cómo causar daño a un personaje con puñetazos en UE4 – Parte 1

Hola, seguimos con esta serie de tutoriales sobre el desarrollo de juegos con Unreal Engine 4. En este tutorial vamos a enseñarle a nuestro personaje sus primeras habilidades para defenderse. Vamos a enseñarle a dar puñetazos. Esto nos servirá para hacer una introducción al Animation Montage, al Animation Composite, a los mecanismo de colisión que nos brinda UE4, a los métodos para causar daño a los Pawn, a reproducir efectos de sonido y muchas cosas más. Lo dividiremos en dos partes por su extensión, aquí vamos con la primera.

Como siempre, vamos a partir del mismo proyecto con el que hemos estado trabajando desde el inicio, pero antes de comenzar con el plato fuerte vamos a hacer una pequeña modificación en la clase del HeroCharacter para facilitar el proceso de cambiar entre un estilo de cámara y otro. Abre el código fuente del proyecto y modifica la clase HeroCharacter para que te quede de la siguiente forma:


//--------------------------------------------------------------------
// File: HeroCharacter.h
//--------------------------------------------------------------------

#pragma once

#include "GameFramework/Character.h"
#include "HeroCharacter.generated.h"

/**  Enum ayudante con los estilos de camara del juego */
typedef enum
{
    CameraGameStyleSideScroller, //Estilo Side Scroller
    CameraGameStyleTopDown       //Estilo Top Down
}
CameraGameStyle;

/** Constante para definir el estilo de juego */
#define GAME_STYLE CameraGameStyleSideScroller

/** Clase base del personaje principal */
UCLASS()
class UE4DEMO_API AHeroCharacter : public ACharacter
{
    GENERATED_UCLASS_BODY()

    /** 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;
    
    /** Cantidad de monedas recolectadas por el personaje */
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Coins)
    int32 CoinsCollected;
    
    /**
     * Se llama cuando el motor detecta la entrada Run
     * Intercambia el estado de correr del personaje
     */
    void ToggleRunState();
    
    /**
     * Inicializa las variables SpringArm y SideViewCamera con la configuracion necesaria
     * para una vista side-scroller
     */
    void InitSideScrollerCamera(const class FPostConstructInitializeProperties& PCIP);
    
    /**
     * Inicializa las variables SpringArm y SideViewCamera con la configuracion necesaria
     * para una vista topdown
     */
    void InitTopDownCamera(const class FPostConstructInitializeProperties& PCIP);
    
    /**
     * Se llama cuando el motor detecta la entrada configurada para 'MoveRight'.
     * En este caso cuando el usuario toca la tecla A o D del teclado
     */
    void MoveRight(float Value);
    
    /**
     *  Se llama cuando se detecta la entrada de tipo MoveForward (W o S).
     *  Determina la dirección en la que está el personaje y le aplica un movimiento (positivo o negativo) en esa dirección
     *
     *  @param Value es igual a 1 cuando se detecta W y -1 cuando se detecta S
     */
    void MoveForward(float Value);
    
    /** 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;
    
    /**
     * Metodo de la clase APawn que permite configurar los 'binding' de los controles
     * Es llamado automaticamente por el Engine
     */
    virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) OVERRIDE;
    
};


//--------------------------------------------------------------------
// File: HeroCharacter.cpp
//--------------------------------------------------------------------


#include "UE4Demo.h"
#include "Coin.h"
#include "HeroCharacter.h"


AHeroCharacter::AHeroCharacter(const class FPostConstructInitializeProperties& PCIP)
    : Super(PCIP)
{
    //Por defecto esta propiedad viene en true para el Character.
    //Pero en nuestro modelo de desplazamiento, no queremos que el personaje rote en base a la rotación del Controller.
    bUseControllerRotationYaw = false;
    
    //Configuración del componente CharacterMovement
    
    //Al estar en true habilita para que el character se rote en la dirección del movimiento al comenzar el movimiento.
    CharacterMovement->bOrientRotationToMovement = true;
    
    //Factor de rotación para la propiedad anterior.
    CharacterMovement->RotationRate = FRotator(0.0f, 540.0f, 0.0f);
    
    //Bajamos un poco el valor por defecto de MaxWalkSpeed para que el personaje camine un poco más lento.
    CharacterMovement->MaxWalkSpeed = 400.0f;
   
    //Segun el valor de la constante GAME_STYLE llamamos al metodo correspondiente para configurar los componentes SpringArm y camera
    switch (GAME_STYLE)
    {
        case CameraGameStyleTopDown:
            InitSideScrollerCamera(PCIP); //Inicializa SpringArm y SideViewCamera para una vista de juego estilo side-scroller
            break;
            
        case CameraGameStyleSideScroller:
            InitTopDownCamera(PCIP);//Inicializa SpringArm y SideViewCamera para una vista de juego estilo top-down
            break;
            
        default: break;
    }

    CoinsCollected = 0;
}

/** Inicializa SpringArm y SideViewCamera para una vista de juego estilo side-scroller */
void AHeroCharacter::InitSideScrollerCamera(const class FPostConstructInitializeProperties& PCIP)
{
    //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);

}

/** Inicializa SpringArm y SideViewCamera para una vista de juego estilo top-down */
void AHeroCharacter::InitTopDownCamera(const class FPostConstructInitializeProperties& PCIP)
{
    CharacterMovement->bConstrainToPlane = true;
    CharacterMovement->bSnapToPlaneAtStart = true;
    
    SpringArm = PCIP.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("CameraBoom"));
    SpringArm->AttachTo(RootComponent);
    SpringArm->bAbsoluteRotation = true;
    SpringArm->TargetArmLength = 800.f;
    SpringArm->RelativeRotation = FRotator(-60.f, 0.f, 0.f);
    SpringArm->bDoCollisionTest = false;
    
    SideViewCamera = PCIP.CreateDefaultSubobject<UCameraComponent>(this, TEXT("TopDownCamera"));
    SideViewCamera->AttachTo(SpringArm, USpringArmComponent::SocketName);
    SideViewCamera->bUseControllerViewRotation = false;
}

void AHeroCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    //Le dice al motor que cuando detecte las entrada de tipo MoveRight que llame al metodo AHeroCharacter::MoveRight
    InputComponent->BindAxis("MoveRight", this, &AHeroCharacter::MoveRight);
    
    //Solo este input se usa en el estilo TopDown
    if(GAME_STYLE == CameraGameStyleTopDown)
        InputComponent->BindAxis("MoveForward", this, &AHeroCharacter::MoveForward);
    
    //Le dice al motor que cuando detecte la entrada de tipo Jump (barra espaciadora) llame al metodo Jump de la clase ACharacter (la clase padre de esta)
    InputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    
    //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);
}

/**
 *  Se llama cuando se detecta la entrada de tipo MoveForward (W o S).
 *  Determina la dirección en la que está el personaje y le aplica un movimiento (positivo o negativo) en esa dirección
 *
 *  @param Value es igual a 1 cuando se detecta W y -1 cuando se detecta S
 */
void AHeroCharacter::MoveForward(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
        //Determina la dirección del movimiento hacia delante
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);
        
        // Crea el vector de la dirección y aplica el movimiento
        const FVector Direction = FRotationMatrix(Rotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, -Value);
    }
}

/**
 *  Se llama cuando se detecta la entrada de tipo MoveForward (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)
{
    //Si el estilo de juego es CameraGameStyleTopDown procesamos la entrada MoveRight para lograr el movimiento que se aplique a este estilo
    if(GAME_STYLE == CameraGameStyleTopDown)
    {
        if ( (Controller != NULL) && (Value != 0.0f) )
        {
            //Determina la dirección del movimiento hacia los lados
            const FRotator Rotation = Controller->GetControlRotation();
            const FRotator YawRotation(0, Rotation.Yaw, 0);
            
            // Crea el vector de la dirección y aplica el movimiento
            const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
            AddMovementInput(Direction, -Value);
        }
        return;
    }
    
    //Si el estilo de juego es CameraGameStyleSideScroller procesamos la entrada MoveRight para lograr el movimiento que se aplique a este estilo
    if(GAME_STYLE == CameraGameStyleSideScroller)
    {
        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);
        }
        return;
    }
}


/**
 * 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;
}

/** 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();
}


Aquí no hemos hecho nada especial. Recuerdas que ya teníamos un método para adaptar el juego para el estilo side-scroller y otro para el top-down? Pues lo que agregamos fue la constante GAME_STYLE y una enum con las dos variantes de estilo que tenemos. Para alternar entre un estilo de cámara y otro basta con modificar el valor de esta constante.

Guarda, compila y ejecuta. Yo además modifiqué el SpringArm del personaje desde el editor en la sección Transform, la propiedad Rotation la tengo en Absolute y con los valores X = 0, Y = – 10 y Z = 200. Esto para darle un poco de inclinación a la cámara, pero es cuestión de gusto, déjala como la teníamos hasta ahora o ponla a tu gusto, como prefieras.

Captura de la vista de nuestro juego

Captura de la vista de nuestro juego

En estos momentos nuestro héroe camina, corre y salta, pero no tiene forma de defenderse, verdad ? . . . solo puede salir corriendo si tiene algún problema :). Pues bien, vamos hoy a enseñarle a dar puñetazos. Lo primero que necesitamos es el FBX de las animaciones de puñetazos. Si le das un vistazo a los recursos que descargaste en el primer tutorial, y que los puedes descargar de aquí si aún no los tienes, verás los siguientes FBX:

MontageExample_Start.FBX: Animación de inicio del movimiento del puñetazo

MontageExample_Punch_1.FBX: Animación del puñetazo con el brazo derecho

MontageExample_Punch_2.FBX: Animación del puñetazo con el brazo izquierdo. Esta animación está hecha como continuación de la anterior, con el objetivo de poder mantener al personaje dando golpes alternando derecha / izquierda sin parar.

MontageExample_End_1.FBX: Animación de fin del puñetazo con la mano derecha

MontageExample_End_2.FBX: Animación de fin del puñetazo con la mano izquierda.

Estos recursos que estamos usando son de unos geniales video-tutoriales que preparó el equipo de Epic Games y los puedes ver aquí. Nosotros los hemos tomado prestados para nuestros tutoriales, pero tienen un problema, estas animaciones de puñetazos NO son hechas para el esqueleto de nuestro personaje, lo que implica que al reproducirlas, nuestro héroe se deformará ligeramente, verás que por ejemplo se le separan un poco las extremidades, esto no es un problema para el desarrollo de este tutorial, pero te lo comento para que no pienses que estas haciendo algo mal :) . . . simplemente no tenemos un animador en disposición de estos tutoriales, por eso estamos usando los recursos que podamos … igual, para aprender y jugar con el UE4 vienen de maravilla :)

Importa estos FBX al proyecto dentro de la carpeta Animation o donde prefieras. Ya sabes, en el Content Browser, Import/Seleccionas los FBX. En la ventana de Import del UE4 selecciona Animation y el esqueleto que usa nuestro personaje y da clic en el Botón Import. Listo, ya tenemos los recursos necesarios, puedes darle doble clic a cada una de las animaciones importadas para que les des un vistazo en el Persona Editor.

Vamos a aprovechar esta situación en la que tenemos una animación en partes para dar una rápida introducción al Animation Composite, uno de los assets de animación que nos brinda el Unreal Engine 4.

Introducción al Animation Composite en Unreal Engine 4

Animation Composite es un animation asset que nos permite unir más de una animación una detrás de la otra y tratar esta secuencia de animaciones como una sola animación. En este ejemplo lo vamos a usar para unir las animaciones MontageExample_Start, MontageExample_Punch_1 y MontageExample_End_1 en una sola animación.

En el Content Browser selecciona: New/Animation/Animation Composite, selecciona el esqueleto de nuestro héroe y ponle de nombre PunchAnimComposite. Dale doble clic para editarlo.

Nuevo PunchAnimComposite

En el panel de configuración tenemos tres secciones, la primera es la sección Composite. Desde esta sección haremos la composición de la animación, arrastrando hasta aquí las distintas animaciones que formarán la secuencia final.

Las otras dos secciones (Notifies y Curves) no las veremos en este tutorial, pero sobre todo los Notifies son súper útiles, nos sirven para definir eventos en puntos exactos de la animación y que podemos usar desde el Blueprint para implementar alguna función determinada en ese preciso momento. Por ejemplo, reproducir un efecto de sonido cuando la animación está en el punto exacto en donde el pie toca el piso y así logramos sincronizar el efecto de sonido de los pasos a la animación de caminar.

Arrastra desde el Asset Browser las animaciones MontageExample_Start, MontageExample_Punch_1 y MontageExample_End_1 a la sección Composite. Fíjate que se crean dos filas y las animaciones se agregan alternadamente entre una y la otra fila para facilitar la visualización. Con los controles de reproducción puedes ver como queda la composición de esas tres animaciones en una sola.

PunchAnimComposite configurado con las 3 animaciones en secuencia

Este nuevo asset de animación que tenemos lo podemos usar como un AnimationSequence normal. Vamos a ver un ejemplo simple. Iré por este proceso sin detenernos mucho ya que es algo que a estas alturas no debes tener problema en entenderlo si has seguido los tutoriales anteriores. Sino, te recomiendo que les des un vistazo primero para que entiendas bien todo lo que haremos aquí.

Primero, desde Edit/Project Settings/Input, agrega una nueva entrada de tipo Action dale de nombre Punch y selecciona la tecla R.

Ventana Edit/Project Settings/Engine – Input con la configuraciones de los Bindings

Abre el Blueprint del Character y crea una nueva variable de tipo bool de nombre IsPunching. Esta variable estará en true cuando el usuario toque la tecla R y en false cuando la suelte. Para lograr esto, abre el Event Graph del Blueprint del Character y agrega lo siguiente:

HeroCharacterBlueprint con el algoritmo a ejecutar cuando se detecte la entrada “Punch“

Hasta ahora, la programación de todo lo que tenía que ver con las entradas del usuario y las acciones que se ejecutaban, y en general la lógica del character, las teníamos implementadas desde C++. Para este tutorial quise hacer esto por aquí para mostrarte la variante blueprint, yo en lo personal este tipo de cosas las prefiero hacer en C++, pero es a gusto de cada cual . . . después me dices cual prefieres tú 😉

Como vez, desde el blueprint podemos agregar un nodo que representa al evento Pressed/Released de cada uno de los inputs que tenemos configurados. Cuando el usuario toque la tecla R (que es la tecla que definimos para el Input de nombre Punch) se va disparar este Evento en el blueprint por el puerto del Pressed, cuando la suelta se dispara de nuevo por el puerto del Released.

Simplemente lo que hacemos es darle valor de true a la variable IsPunching cuando se presione la tecla R y de false cuando se suelte.

Ahora abre el AnimationBlueprint del Character, agrega una nueva variable con el mismo nombre, isPunching también de tipo bool. No es necesario que la variable tenga el mismo nombre pero creo que es más claro.

Modifica el Event Graph del AnimationBlueprint del Character para que te quede de la siguiente forma:

Animation Blueprint del Character donde le agregamos el proceso para darle valor a la variable IsPunching según el valor que tenga la variable de mismo nombre en el Character. Recuerda que en el Character la variable IsPunching toma valor según se presione o no la tecla R (“Punch” input)

Recuerda que este algoritmo se ejecuta constantemente en cada update del Animation.

Muy bien, ahora pasa a la maquina de estado que tenemos para el Character y modifícala para que te quede de la siguiente forma:

Máquina de estado del Character con el nuevo estado Punch. A este estado se llega desde el Idle/Walk si IsPunching está en true. Se regresa al Idle/Walk cuando se termina de reproducir la animación

Nada nuevo aquí tampoco, con esto acabamos de darle un nuevo estado al personaje (Punch), a este estado entrará cuando esté golpeando y al terminar de golpear volverá al Idle/Walk. Al agregar el nodo PunchAnimComposite al Final Animation Pose dentro del estado Punch, asegúrate de desmarcar la opción de Loop, ya que no queremos que esta animación se reproduzca en loop.

Compila y ejecuta el juego:

Captura de pantalla del juego en ejecución. Cuando se presiona la tecla R el personaje reproduce el PunchAnimComposite ejecutando toda la secuencia de animaciones que conforman el puñetazo.

Vale, esto a primera vista se ve bastante bien, pero con este mecanismo tenemos varios problemas. Primero, al tenerlo como un estado en el Locomotion State Machine del personaje, solamente se podrá reproducir la animación del golpe en cada momento. No podrá fusionar dos animaciones. Por ejemplo, si queremos que pueda golpear en lo que camina tendremos un problema. Prueba para que veas, camina, y en lo que te desplazas toca la R para golpear, verás como se reproduce la animación del golpe pero en ese momento se estará desplazando por el escenario patinando. Como único pudiéramos solucionar esto es cancelando el desplazamiento cuando se esté moviendo, aunque no es una mala idea, no es lo que queremos hacer, queremos algo mas cool !! :)

De cualquier forma, creo que este simple ejemplo ha servido para ver el Animation Composite que en muchos casos nos puede ser útil. Pero este no es el plato fuerte de este tutorial, vamos con algo mucho más genial que nos brinda el UE4 y es el Animation Montage.

Introducción al Animation Montage en Unreal Engine 4

El AnimMontage es un animation asset que nos otorga una libertad fenomenal para el trabajo con animaciones. Con este tipo de asset podemos exponer el control de la animación hacia el Blueprint, permitiéndonos en cualquier momento, pausar la animación, reproducir una sección determinada de la animación, definir ”al vuelo“ que animación se va a reproducir después de la actual, saltar a reproducir otra animación, loopear de forma súper simple un grupo de animaciones y disparar eventos en cualquier punto de la animación, semejante a los Notifies. En fin, nos da un control total de las animaciones a nivel de código (ya sea C++ o VisualScripting).

En este ejemplo usaremos un AnimMontage para configurar las animaciones relacionadas con los puñetazos del personaje, ya que en el proceso de golpear tenemos que tener total control de la animación en cada momento.

En el Content Browser dentro de la carpeta Character da clic en New/Animation/AnimMontage, selecciona el esqueleto que usa nuestro personaje. Ponle de nombre PunchingAnimMontage y dale doble clic para editarlo.

PunchingAnimMontage acabado de crear y abierto en el Persona Editor

El panel de configuración del AnimMontage es algo parecido al AnimComposite. La primera sección es Montage, aquí es donde agregaremos todas las animaciones que conformarán el AnimMontage, pero a diferencia del AnimComposite, estas animaciones no tienen que ser una secuencia. La idea es que sean animaciones relacionadas a una acción determinada pero que no precisamente las reproduciremos una detrás de otra, sino que podremos crear secciones para seleccionar en cada momento que animación o animaciones reproducir.

Primero tenemos que definir un Slot Name para este AnimMontage. En el campo Slot Name que tiene el alert en rojo “Please provide a slot name for this asset“ escribe PunchSlot

Arrastra desde el Asset Browser hasta la sección Montage las animaciones MontageExample_Start, MontageExample_Punch_1, MontageExample_Punch_2, MontageExample_End_1 y MontageExample_End_2. Las animaciones se agregan alternándose la fila para facilitar la visualización.

PunchingAnimMontage en edición: Agregadas todas las animaciones que formaran el AnimMontage

En este punto, si pre-visualizas la animación, esta no tiene mucho sentido. Se reproducen todas estas animaciones en secuencia, cuando no son una secuencia en realidad. Pues bien, el AnimMontage nos permite, a partir de secciones que definamos, hacer una mezcla entre estas animaciones y después decidir cual vamos a reproducir en cada momento.

Ahora vamos a crear las secciones que tendrá este AnimMontage. En la primera fila del panel Montage da clic derecho y selecciona New Montage Section y ponle de nombre PunchStart. Fíjate que se agrega una línea verde clara vertical y en la primera fila se muestra el nombre de esta sección. Ahora, por defecto tenemos una sección de nombre Default al inicio del gráfico, pon el cursor sobre la línea verde que representa el inicio de esa sección Default y arrástrala hacia delante de la sección PunchStart que acabamos de crear, verás que automáticamente la sección PunchStart se posiciona en el inicio. Ahora puedes dar clic derecho sobre esta sección Default y eliminarla, ya que no la vamos a usar.

Crea una nueva sección y llámala Punch1 arrastra la línea verde de esta sección para que quede exactamente en la unión entre las animaciones PunchStart y Punch1. Notarás que con la rueda del mouse puedes hacer zoom en esta gráfica para ver bien la unión entre las dos animaciones, también notarás que el Editor te ayuda a posicionar la sección en la intersección de dos animaciones.

Repite el proceso creando 3 secciones más de nombre Punch2, PunchEnd1 y PunchEnd2 y posiciónalas al inicio de la animación que le corresponde. Lo que acabamos de crear aquí son las distintas secciones que componen el animation montage. Te quedará de la siguiente forma:

Panel Montage del PunchingAnimMontage después de agregar y posicionar las secciones.

Ya tenemos las secciones que conforman nuestro AnimMontage solo nos falta el último paso: Configurar la relación entre cada una de las secciones. Fíjate que debajo de la zona Montage tienes la zona Sections con dos botones Create Default y Clear. Primero da clic en el botón Clear para restaurar cualquier configuración que tomen por defecto las secciones. Debajo de estos botones tendrás la lista de botones alineados horizontalmente que representan cada una de las secciones que acabamos de crear y debajo de esos botones tendremos finalmente como relacionaremos cada una de las secciones. Después de dar en el botón Clear tienes 4 fila que representan cada una de las secciones en donde ninguna sección tiene relación con otra.

Vamos a modificar esto, vamos conformar la primera sección a partir de la animación PunchStart después de reproducirse PunchStart queremos que seguidamente se reproduzca Punch1 para lanzar el puñetazo con la mano derecha. Bien, vamos a hacer esto hasta aquí.

Primero da clic en el botón verde que dice PunchStart de la primera fila, debajo del botón Preview All Sections, notarás que se pondrá en amarillo ahora toca de los botones alineados horizontalmente arriba, el de nombre Punch1. Verás que automáticamente se agrega el Punch1 a continuación del PunchStart. Te quedará así:

tuto5_imagen_12

Muy bien, ahora vamos a complicar un poquito más la estructura de esta sección. Cuando el personaje esté dando puñetazos lo que queremos es que si se deja la tecla R presionada el personaje continúe dando golpes indistintamente con la mano derecha y la izquierda. Muy bien, pues para esto tenemos que crear un ciclo entre estas dos animaciones Punch1 y Punch2. O sea que el proceso completo sería Punch Start -> Punch 1 -> Punch2 -> Punch 1 -> Punch2 … y así.

Toca en el botón Punch1 de la primera fila debajo del botón Preview All Sections , se te pondrá en amarillo. Ahora toca de los botones alineados horizontalmente arriba el de nombre Punch2. Verás que automáticamente, al igual que con el caso anterior, se te agrega el Punch2 a continuación del Punch1.

Ahora solo nos queda definir para que estas animaciones se mantengan reproduciéndose en ciclo. Pues para esto es tan simple como tocar el botón Punch2 de la primera fila debajo del botón Preview All Sections, cuando se ponga en amarillo toca de los botones alineados horizontalmente arriba, el de nombre Punch1, verás como a diferencia de antes que se agregaba a continuación, ahora se ponen Punch1 y Punch2 en azulitos. Lo que quiere decir que estas dos animaciones se van a reproducir en ciclo. La configuración de la primera sección te quedará así:

Configuración final de las secciones del PunchingAnimMontage

Con esto terminamos de configurar las secciones que componen nuestro PunchingAnimMontage. Fíjate que puedes dar en el botón Preview al inicio de cada fila para que veas la pre-visualización de cada sección. Las dos últimas secciones las queremos así de simples, con una sola animación, ya que las usaremos para llamar a una de las dos, dependiendo en el punto en el que el jugador deje de presionar la tecla R, para empatar el medio del puñetazo con el final de este para el brazo correcto.

Perfecto ¡! Ya tenemos listo el PunchingAnimMontage compila y guarda los cambios. Ahora hay que modificar el Animation Blueprint de nuestro personaje para que use este Montage.

Primero abre el Locomotion State Machine del personaje y elimina el estado Punch que creamos para ver el ejemplo del AnimComposite. Ahora, abre el AnimGraph, en este momento nuestro AnimGraph tiene el nodo de la maquina de estado conectado directamente al Final Animation Pose. Pues bien, para reproducir animaciones desde el código usando AnimMontage necesitamos un nodo especial en el AnimGraph, un nodo de tipo “Slot“. Da clic derecho en el AnimGraph y selecciona dentro de la sección Blends, Slot (No slot name). Esto agregará un nodo de tipo Slot al AnimGraph. A este nodo tenemos que decirle que AnimMontage representará y para esto basta con seleccionarlo y en el panel de propiedades darle el mismo nombre que pusimos en el campo Slot en el PunchingAnimMontage cuando lo creamos, que fue: PunchSlot. Ahora conecta el StateMachina al Slot y este al Final Animation Pose. Listo !! , te quedará así:

AnimGraph del AnimationBlueprint del personaje con el PunchSlot para usar el PunchingAnimMontage

Muy bien, ahora pasa al EventGraph y modifícalo para que te quede de la siguiente forma:

Captura del EventGraph donde hemos agregado la lógica para usar el PunchingAnimMontage. (puedes dar clic para agrandar la imagen)

Vamos a analizar lo que hemos hecho aquí, aunque estoy bastante seguro que si has seguido los tutoriales anteriores no tendrás ningún problema con entender esto. Desde el EventGraph podemos usar varios nodos para trabajar con los AnimMontage. En este caso usamos dos de ellos. El Montage Is Playing que nos permite saber si se está reproduciendo alguna animación de las que están en un AnimMontage y Montage Play que nos permite reproducir un AnimMontage determinado. Al usar el Montage Play se reproduce la primera sección que tenemos definida en el AnimMontage, lo que quiere decir, que en este caso cuando llegue aquí se va a producir la secuencia PunchStart->Punch1Punch2.

En el algoritmo lo que hacemos es determinar el valor de IsPunching, si este está en true preguntamos si se está reproduciendo el PunchingAnimMontage, fíjate que el segundo puerto (o parámetro) de este nodo, nos permite definir a que AnimMontage nos referimos. Entonces, si no se está reproduciendo es que vamos a reproducir el PunchingAnimMontage. Aquí igual, el parámetro Montage To Play nos permite definir que AnimMontage reproducimos, incluso nos permite indicar una velocidad de reproducción mediante el parámetro In Play Rate.

Esta validación del Montage Is Playing la hacemos porque recuerda que este algoritmo se reproduce en cada update del proceso de animación del personaje, y en el tiempo en el que se reproduce cualquiera de las secciones que tenemos definidas en el PunchingAnimMontage este algoritmo se ejecuta más de una vez. Si no hacemos esto, en todo momento estaríamos interrumpiendo la animación para comenzarla de nuevo.

Compila, guarda, ejecuta el juego, y toca la tecla R. Perfecto !!, ya nuestro personaje está dando puñetazo como un loco, fíjate que comienza la animación del puñetazo y después se queda alternando entre el golpe con la mano derecha y con la izquierda. Pero tenemos un problema, a pesar de soltar la tecla R nuestro héroe se queda como loco sin dejar de lanzar puñetazos.

Si analizas lo que hicimos, tiene lógica que esto pase, recuerda que el Montage Play lanza la reproducción de la primera sección que tenemos definida en el AnimMontage, y la primera sección que tenemos en el PunchingAnimMontage no termina, porque después del PunchStart entra en un loop entre Punch1 y Punch2 y de ahí no sale nunca. Pues bien, vamos a solucionar esto.

Recuerdas que tenemos además de esa primera sección en el PunchingAnimMontage dos secciones más no? PunchEnd1 y PunchEnd2 que son la animación del terminado del puñetazo para la mano derecha e izquierda respectivamente. Pues bien, vamos a usarlas, pero tenemos un problema, ¿cómo saber cuando el usuario suelta la tecla R exactamente que mano es la que está lanzando el golpe, para a partir de ahí reproducir la correspondiente animación de fin del golpe según la mano y terminar le proceso de puñetazo ? Pues aquí es donde entra a ayudarnos otra genial funcionalidad del AnimMontage, los Brach Points.

Configurando los Branch Points en el PunchingAnimMontage

Branch Points es la forma que tenemos de lanzar eventos que podemos capturar en el código en puntos exactos de cualquiera de las animaciones que forman parte del Montage, son similares a los Notifies solo que los Notifies son asincrónicos y los Brach Points son sincrónicos. Esto quiere decir que estos últimos tienen más precisión en el tiempo que los Notifies, pero por supuesto, requieren de más procesamiento.

Abre el PunchingAnimMontage y en la cuarta fila del grafico en la sección Montage, debajo de la animación Punch1, da clic derecho/ New Branch Point y ponle de nombre IsPunching1. Ahora arrastra este branch point para colocarlo casi al final de la gráfica del Punch1. Repite el proceso para agregar otro branch point de nombre IsPunching2 debajo de la animación Punch2 y colócalo casi al final de la gráfica de esta animación. Te quedará de la siguiente forma:

Branch Points agregados al final de las animaciones Punch1 y Punch2 en el PunchingAnimMontage.

Esto que acabamos de hacer nos permitirá ejecutar un código determinado en el Blueprint inmediatamente que la reproducción de la animación pase por este punto. O sea, que cuando el personaje esté reproduciendo la animación del puñetazo, casi al terminar la animación del brazo derecho se va a lanzar el evento IsPunching1 y cuando esté terminando con el brazo izquierdo se va a lanzar el evento IsPunching2.

Ahora lo que necesitamos es intervenir estos eventos y preguntar si IsPunching está en false, o sea si el usuario ya soltó la tecla R, y si es así, actualizamos para que al terminar esa animación pase a reproducir la animación de fin según el brazo que sea.

Abre el EventGraph del AnimationBlueprint del personaje. En un espacio en blanco da clic derecho y con el check Context Sensitive marcado fíjate que tienes una sección que dice Add Montage Branching Point Event y esta sección lista los dos branch points que acabamos de crear. Agrégalos los dos al blueprint y continua modificándolo para que te quede de la siguiente forma:

Trozo del AnimationBlueprint del personaje donde se capturan los eventos IsPunching1 y IsPunching2 del AnimMontage para definir la siguiente sección a reproducir del PunchingAnimMontage

Para el trabajo con Montage desde el blueprint también contamos con el nodo Montage Set Next Section. Este nodo nos permite definir cual es la siguiente sección que se va a reproducir después que termine una determinada. Con el parámetro Section Name to Change definimos cual es de la que vamos a partir, en cada caso según el evento, será Punch1 o Punch2 y con el parámetro Next Section definimos la siguiente sección que se va a reproducir, que igual, según sea el evento sería PunchEnd1 o PunchEnd2.

También contamos con el nodo Jump Tu Section, que nos permite exactamente en ese momento interrumpir la animación que se está reproduciendo y reproducir otra sección.

Listo, guarda, compila y ejecuta el juego. Toca la tecla R indistintamente, prueba tocarla y dejarla unos segundos y después soltarla. Notarás que cuando se suelta la tecla R según la mano en la que se quedó dando el puñetazo es la que se usa para terminar la animación del puñetazo … genial, verdad ¡!!??

Vale, pero aún nos va quedando un problemita. Si intentas caminar en lo que estás dando golpes verás que patinas en vez de caminar. Como dijimos, esto lo podemos solucionar simplemente eliminado la posibilidad de caminar cuando se esté dando golpe, pero eso no es lo que queremos en nuestro juego, en nuestro juego queremos poder salir corriendo sin parar y dando puñetazos “de todos colores“ :)

Pues bien, para lograr esto tenemos que usar en el AnimGraph un nodo súper útil que se llama Layered blend per bone. Este nodo nos permite fusionar distintas animaciones en una, pudiendo definir que una parte del esqueleto reproduzca una animación y la otra parte otra animación. Es esto exactamente lo que necesitamos, queremos que la animación del puñetazo se reproduzca en la parte de arriba del esqueleto y la de caminar en la parte de abajo.

Abre el AnimGraph del AnimationBlueprint del personaje. Ahora mismo lo tenemos configurado de la siguiente forma:

AnimGraph del personaje antes de usar el Layered blend per bone

Da clic derecho en una zona en blanco y agrega el nodo de nombre Layered blend per bone. Por defecto este nodo solo tiene un puerto de entrada de nombre Base Pose y otro de salida. Selecciona el nodo, da clic derecho sobre él, y después da clic en Add Blend Pin. De esta forma agregamos un segundo puerto de entrada al nodo.

Muy bien, pero si te fijas tenemos un problema. Nosotros en este visualscript en la salida del StateMachine tenemos la animaciones de locomoción del personaje, y en la salida del PunchSlot tenemos la animación del puñetazo, pero Layered blend per bone necesita también como parámetro lo que nos retorna el StateMachine en limpio. Por tanto, necesitamos el StateMachine conectarlo a dos puertos de entrada y sin embargo este tiene un solo puerto de salida y no lo podemos duplicar. Bien, solucionar esto es muy simple. Tenemos que usar un nodo que nos permite guardar en memoria (en caché) la salida del StateMachine y este nodo si lo podemos duplicar todas las veces que queramos en el blueprint.

Usando el nodo Save Cache Pose para guardar en caché el resultado del SateMachine y poder usarlo varias veces en el blueprint.

En un espacio en blanco del blueprint da clic derecho y con el check de Context Sensitive marcado agrega un New Save cached pose… dentro de la sección Cached Poses. Ahora conecta la salida del StateMachine a este nodo. Bien, ya tenemos en cache el State Machine. Ahora vuelve a dar clic en una zona en blanco y con el check Context Sensitive marcado, agrega Use cached pose ‘HeroLocomotionCache‘. Este nodo es una referencia a esa caché en donde tenemos la salida del StateMachine, y lo mejor que tiene es que lo podemos duplicar todas las veces que necesitemos.

Crea un duplicado de este nodo y conecta uno de ellos al primer puerto de parámetro del Layered blend per bone. El otro conéctalo al PunchSlot y la salida del PunchSlot conéctala al segundo parámetro del Layered blend per bone. Por último el Layered blend per bone conéctalo al Final Animation Pose. Te quedará el gráfico de la siguiente forma:

AnimGraph del AnimationBlueprint del personaje haciendo uso del Layered blend per bone y la caché del StateMachine.

Muy bien, ahora solo nos falta definir en el Layered blend per bone cual va a ser la parte del esqueleto que usaremos para dividir la animación. Selecciona el Layered blend per bone y en el panel de propiedades de este nodo en la sección Config tiene la propiedad Layer Setup que es un arreglo con un solo elemento. Despliega el elemento 0 y este también es un arreglo con un solo elemento de nombre Branch Filters. En este punto, Branch Filters no tiene ningún elemento. Da clic en el botón de + para agregar un elemento y en la propiedad Bone Name escribe spine_01. Spide_01 es el nombre del hueso que vamos a usar para el blend, puedes dar clic en el Modo Skeleton (en la esquina superior derecha) para que veas la estructura del esqueleto, busca y selecciona el spide_01 para que veas que hueso es y que parte del cuerpo controla.

Finalmente el AnimGraph te quedará de la siguiente forma:

AnimGraph del AnimationBlueprint. La zona marcada en rojo es la configuración del Layered blend per bone donde definimos el hueso para hacer el blend de las dos animaciones.

Listo, compila, guarda y ejecuta el juego. Una cosa importante a mencionar y que notarás en cuanto tires el primer puñetazo. Verás que el modelo del personaje se deforma un poco, esto pasa por lo que hablamos al inicio, estas animaciones que puñetazos que estamos usando NO son para este esqueleto. Por supuesto que en nuestro juego vamos a tener un animador que nos dará los FBX correctos para el esqueleto de nuestro personaje :)

Ahora prueba correr y dar puñetazos al mismo tiempo, verás que funciona a la perfección, a medida que la parte inferior del personaje mueve los pies con la animación de caminando o corriendo la parte de arriba reproduce al puñetazo. Tal como queríamos.

Conclusión

Ya nuestro personaje tiene las habilidades de dar puñetazos, así que ya se puede defender. Vamos a dejar esta primera parte aquí, en la segunda parte de este tutorial vamos a agregar a un enemigo para golpearlo, y entrenar un poco con él :), en cada golpe el enemigo recibirá daño y cuando su salud se termine morirá. Eso nos permitirá dar una introducción a los mecanismos de colisión que nos brinda el UE4, a los mecanismo de daño, a como reproducir efectos de sonidos, veremos como usar los geniales recursos que tenemos a la mano en el marketplace y varias cosas más. No te lo pierdas, puedes seguirme en Twitter (@nan2cc) para que estés al tanto.

Mientras, te recomiendo que le des un vistazo a la serie de video-tutoriales que te comenté al inicio: Introduction to Third Person Blueprint Game, y sinceramente de donde tomé la idea y los recursos para este tutorial :). Es en inglés, pero aunque no se te de bien el inglés, después de pasar por este tuto podrás entender muy bien todo. Además verás un ejemplo de uso de los Notifies que comentamos aquí. Te recomiendo no te lo pierdas.

Hasta la próxima !!