Archivo de la etiqueta: damage

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.

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 !!