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

Fernando Castillo Coello
Follow me

Fernando Castillo Coello

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

8 pensamientos en “Cómo causar daño a un personaje con puñetazos en UE4 – Parte 1

  1. CobaltUDK

    Buenas, ¿sigue UE4 sin tener un comando para reproducir directamente animaciones? Al estilo del “playcustomanim” de UDK.

    Cuando salió UE4 me dijeron los de Epic que no lo llevaba aún pero que se lo iban a implementar en futuras actualizaciones.

    A mí lo de usar montages y blueprints para hacer lo que en UDK se hace con un simple comando, la verdad que no me hace mucha gracia.

    Responder
    1. nan2cc

      Hola CobaltUDK, no conozco el playcustomanim de UDK (nunca llegué a trabajar mucho con UDK) así que no sabría decirte exactamente si UE4 tiene algo parecido. A que te refieres exactamente con: tener un comando para reproducir directamente animaciones?

      De cualquier forma, creo que el AnimMontage es una herramientas más, y muy útil, que nos sirve no solo para reproducir una animación determinada a código, sino que podemos mezclarlas y controlar a nuestro antojo la reproducción. Tal como hacemos en el tutorial. Aquí usamos blueprint, pero lo mismo lo podemos hacer desde C++ si lo prefieres.

      Saludos

      Responder
      1. CobaltUDK

        Sí, el comando sirve exactamente para reproducir una animación del animset:

        function float PlayCustomAnim (name AnimName, float Rate, optional float BlendInTime, optional float BlendOutTime, optional bool bLooping, optional bool bOverride)

        Montage va bien para hacer cosas más elaboradas y mezclar varias animaciones, pero en un juego típico la mayoría de las veces se reproducen animaciones independientes, y el tener que hacer un montage para cada una de las animaciones del animset me pareció absurdo.

        No sé, me parece una de los comandos más básicos de un engine de videojuegos, y que UE4 no lo llevara me sorprendió mucho. Un empleado de Epic me dijo en los foros que estaba previsto incluirlo para siguientes versiones, por eso preguntaba. Pero viendo la deriva que llevan hacia la “programación visual” y demás, no me soprendería que aún no lo llevase.

        Por otro lado echaba de menos tutoriales en español, y sobre todo tutoriales de cómo hacer las cosas con C++. Lo que vi estaba todo orientado a blueprint, que no me gusta nada. Así que gracias por hacerlos :)

        Responder
        1. nan2cc

          Hola CobaltUDK,

          Disculpa la demora en responder, es que hemos estado con el update de nuestro website y ya imaginas … un poco de locos … pero ya está online el nuevo look. Dicho sea de paso, te invito a que le des un vistazo http://www.spissa.com 😉

          Entonces, lo que hablábamos… no se si es exactamente lo que quieres pero también puedes reproducir una animación determinada directamente desde el código con el método PlayAnimation del Mesh del Pawn.

          Por ejemplo, desde el Blueprint del character puedes hacer algo como esto:

          Ejemplo de Play Animation desde el Blueprint

          En este caso, al detectar el input Punch el personaje reproduce la animación de MontageExample_Punsh2 que es directamente un AnimSequence, no es un Montage.

          Por otro lado, si prefieres hacerlo desde C++ puedes hacer lo siguiente:

          //Cargando el AnimSequence desde el constructor del Character
          static ConstructorHelpers::FObjectFinder<UAnimationAsset> AnimSequence(TEXT("/Game/Animations/MontageExample_Punch_2.MontageExample_Punch_2"));
          MyCustomAnim = AnimSequence.Object; //MyCustomAnim es un atributo de la clase declarada en el .h de la siguiente forma: UAnimationAsset * MyCustomAnim;
          
          //Después para reproducir la animación, en donde quieras, puedes usar:
          Mesh->PlayAnimation(MyCustomAnim, false); //El segundo parametro es: bLooping
          

          Te sirve esta variante ?

          Saludos, Fernando

          Responder
  2. Pingback: Implementando un AI Character con un arma en UE4 | El blog de Fernando Castillo Coello

Deja un comentario

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

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