Archivo de la etiqueta: AnimMontage

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.

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