Hola,
Los invito a una serie de tutoriales sobre el desarrollo de juegos en Unreal Engine 4.
1 - Introduccin al desarrollo de video juegos con Unreal Engine 4
Este primer tutorial pretende dar una introduccin al Unreal Engine 4. Crearemos la
base de nuestro juego, donde tendrs al personaje protagnico caminando por el nivel
usando una cmara fija, con controles bsicos. Este simple inicio nos permitir aprender
a importar los modelos 3D al proyecto. Crear la clase necesaria para controlar al
personaje. Entender la filosofa que sigue el framework de Unreal en su modelo de
clases. Una introduccin a la programacin en Unreal Engine usando C++. La
comunicacin entre C++ y el Editor. Los mecanismos de animacin del personaje y una
introduccin al Visual Scripting usando el Blueprint Editor.
2 - Tutorial: Como hacer un juego side-scroller 3D con Unreal Engine 4
En este segundo tutorial vamos a configurar la cmara del juego para lograr una vista
side-scroller. Vamos a agregar unas monedas al escenario y usar un mecanismo simple
de colisin para que el personaje las pueda recolectar, y veremos como debuguear las
colisiones en nuestro nivel. Vamos a ensearle a nuestro personaje a correr y saltar .
Veremos varios macros de variables y funciones de clase para la integracin entre el
cdigo C++ y el Editor . . . y muchas cosas ms. No te lo pierdas !!
3 - Introduccin a la Inteligencia Artificial en Unreal Engine 4
En este tutorial vamos a agregar un enemigo que estar patrullando una zona del nivel.
Cuando nos acerquemos a esa zona y el enemigo se de cuenta que estamos cerca, nos
perseguir, y si pierde nuestro rastro, volver a su tarea de vigilante. Con este simple
ejemplo veremos varios conceptos relacionados con la inteligencia artificial en Unreal
Engine 4 como el Behavior Tree, Decorators, Task, Services, BlackBoard, AIController
etc.
4 - Introduccin a la IA en UE4 parte 2 (la variante en C++)
En este tutorial no vamos a implementar ninguna funcionalidad nueva en nuestro juego.
Vamos a implementar las mismas acciones que tiene el NPC del tutorial pasado, pero en
C++. Esto nos va a servir para acercarnos un poco ms al Framework C++ que nos
brinda el Engine. Veremos como incluir nuevos mdulos al proyecto. Cmo iterar por
objetos del nivel. Cmo manipular la informacin guardada en el Blackboard que usa el
AIController desde C++ y cmo crear Task y Services para el Behavior Tree totalmente
desde C++.
5 - Cmo causar dao a un personaje con puetazos en UE4 Parte 1
En este tutorial vamos a ensearle a nuestro personaje sus primeras habilidades para
defenderse. Vamos a ensearle a dar puetazos. Esto nos servir para hacer una
personaje muere, cuando las fuerzas enemigas comienzan a atacar nuestra base, cuando
alcanzamos un objetivo determinado del juego . . . en fin, en muchsimos casos. Es
precisamente en estas situaciones donde nos vienen a ser de gran ayuda los Delegates.
Espero que te sea til y te guste, si es as, djame saber tus comentarios y comprtelo
con el resto de tus amigos tambin apasionados por el desarrollo de video juegos con
Unreal Engine 4. Puedes seguirme en Twitter (@nan2cc), para que estes al tanto de los
proximos tutoriales.
Saludos
Un proyecto en Unreal Engine 4 est compuesto bsicamente de dos grandes piezas que
trabajan en conjunto. Los niveles, que es lo que se trabaja en el Editor y el proyecto de
programacin, que trabajamos en el IDE de programacin. Vimos como al crear un
nuevo proyecto en UE4 se crean ambas partes. Ahora vamos a trabajar en la segunda
parte, la parte del cdigo.
En Unreal Engine 4 se programa en C++, al crear un nuevo proyecto, automticamente
se crea un proyecto en el XCode (o Visual Studio si usas Windows) con las clases
bsicas para nuestro juego. Abre tu IDE de programacin con el proyecto creado.
Dentro de la carpeta Source es que se encuentran los fuentes nuestros. Dentro de la
carpeta Engine estn todos el framework. Tener acceso a esto es genial, porque sirve de
mucha ayuda para revisar como estn implementadas las clases, o para que es una
determinada propiedad, viendo los comentarios puestos por el propio equipo de Epic.
Antes de crear nuestra primera clase vamos a comentar rpidamente la filosofa que
sigue Unreal Engine en su framework.
En UE4 todos los elementos que aparecen en nuestro juego son Actors (heredan de la
clase AActor). Una silla, una mesa, un enemigo o el personaje principal. Los elementos
del juego que son controlados, o sea que no son estticos, que tienen un
comportamiento, son Pawns. Hay un tipo especial de Pawn que es el Character. El
Character es el Pawn que representa al personaje principal y tiene implementaciones
particulares que solo tendr el Pawn que ser controlado por el jugador. Por ejemplo, si
en nuestro juego tenemos al personaje principal y a un enemigo. El personaje principal
ser un Character y el enemigo ser un Pawn solamente. Ahora todos los Pawns son
controlados por una clase Controller, para el caso del Character, este es controlado por
un PlayerController. El PlayerController es la clase que recibe las entradas del jugador,
del ser humano, y mediante ellas controla al personaje en el juego, al Character.
Bsicamente el PlayerController representa al ser humano, el Character (Tipo especial
de Pawn) representa al personaje dentro del juego y es controlado por el
PlayerController. Mientras que los otros Pawns pueden ser controlados, por ejemplo,
por AIController.
. . . sip :S, bastante enredado, pero poco a poco a medida que te familiarices con Unreal
Engine dominars esta filosofa, la jerarqua de clases y la relacin entre ellas.
Volviendo al cdigo, dentro de la carpeta Source tenemos una carpeta con el nombre
que le dimos al proyecto, en mi caso UE4Demo y dentro unas pocas clases con las que
comenzar nuestro juego.
La primera clase a tener en cuenta es UE4DemoGameMode esta es la clase que define
el GameMode de nuestro juego. En Unreal la clase GameMode define las reglas del
juego, por ejemplo, las condiciones en las que se gana, las condiciones en las que se
pierde etc, adems es la encargada de definir el PlayerController, el Pawn por defecto,
entre otras muchas cosas. Es el ncleo del juego. Si abrimos el .h veremos que es una
clase que hereda de AGameMode y de momento no tiene ms nada.
1
2
3
4
//AUE4DemoGameMode.h
#pragma once
#include "GameFramework/GameMode.h"
#include "UE4DemoGameMode.generated.h"
5
6
7
8
9
10
11
UCLASS()
class AUE4DemoGameMode : public AGameMode
{
GENERATED_UCLASS_BODY()
};
Como notars de seguro, la clase tiene en su declaracin dos macros que te llamarn la
atencin, UCLASS() y GENERATED_UCLASS_BODY
Unreal Engine posee un robusto sistema para el manejo de objetos. La clase base para
los objetos en Unreal es UObject. el macro CLASS puede ser usado en clases que
derivan de UObject, de esta forma el sistema manejador de UObjects es avisado de la
existencia de esta clase.
Al incluir estos macros logramos que la clase a bajo nivel sea tratada por los
mecanismos de Unreal como el Recolector de basura, Serializacin, Inicializacin
automtica de las propiedades, Integracin automtica con el Editor etc.
Ahora vamos a ver la implementacin de nuestro GameMode. Como vers en el
UE4DemoGameMode.cpp tendrs solamente la implementacin del constructor.
1
2
3
4
5
6
7
8
9
1
0
//AUE4DemoGameMode.cpp
#include "UE4Demo.h"
#include "UE4DemoGameMode.h"
#include "UE4DemoPlayerController.h"
AUE4DemoGameMode::AUE4DemoGameMode(const class
FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
PlayerControllerClass = AUE4DemoPlayerController::StaticClass();
}
Blueprint Editor en el modo Components. Desde aqu podemos configurar todo nuestro
Character.
A la izquierda del editor tenemos el panel Components este panel contiene todos los
componentes que conforman el Character al seleccionar uno, en el panel de abajo se
muestran las propiedades de ese componente. El CharacterMovements como el nombre
lo indica es el componente que contiene las propiedades que afectan el movimiento del
Character, por ejemplo, aqu tenemos Max Walk Speed que es el mximo de velocidad
que toma el personaje al desplazarse, hay muchsimas ms propiedades, dale un vistazo
a todas por arriba para que tengas una idea de todo lo que se puede configurar en el
Character con respecto al movimiento.
El otro componente que tiene un Character es el CapsuleComponent. El
CapsuleComponent es usado para la deteccin de colisiones con el personaje. Es esa
capsula transparente que se ve en el Viewport del Editor y es la zona de colisin del
personaje.
Por ltimo dentro del CapsuleComponent tenemos un Mesh, que como ya te imaginars
es el Mesh que representa a nuestro personaje. Adems hay un ArrowComponent que
nos ayuda para saber la direccin del Character.
Bien, el primer paso ser acabar de configurar el Mesh de nuestro Character. Selecciona
en el panel de Componentes el componente Mesh y en el panel detalles en la seccin
Mesh tienes la propiedad Skeletal Mesh despliega el combobox que hay aqu y
selecciona el nico Skeletal Mesh que tenemos en nuestro proyecto que creamos al
importar el fbx de nuestro hroe. Al hacer esto en el Viewport se ver el modelo de
nuestro hroe. Usa las herramientas de traslacin y rotacin para colocar el Mesh dentro
del CapsuleComponent y mirando en la misma direccin que el Arrow Component. Por
ltimo da clic en el botn Save en la esquina superior derecha del Editor.
6
7
8 HeroCharacterBlueprint creado y configurado desde el Editor
static ConstructorHelpers::FObjectFinder<UClass>
9 PlayerPawnBPClass(TEXT("Class'/Game/Character/HeroCharacterBlueprint.
1 HeroCharacterBlueprint_C'"));
0
1
//Inicializa el atributo DefaultPawnClass con el
1 HeroCharacterBlueprint creado y configurado desde el editor
if (PlayerPawnBPClass.Object != NULL)
1
{
2
DefaultPawnClass = PlayerPawnBPClass.Object;
1
}
3}
1
4
Vamos a dar un stop aqu para explicar que acabamos de hacer con estas pocas lneas
porque aunque tengas experiencia en C++ de seguro que esta sintaxis te parecer algo
rara. En la primera lnea lo que hacemos es buscar y obtener la instancia de la clase
HeroCharacter creada en el editor mediante el Blueprint Editor. Creamos una variable
del tipo FObjectFinder, FObjectFinder es una estructura parametrizada publica que se
encuentra dentro de otra estructura de nombre ConstructorHelpers. FObjectFinder
recibe en su constructor la direccin del objeto que vamos a instanciar, si el objeto es
encontrado satisfactoriamente su instancia se almacena en la propiedad Object.
Un buen consejo, como tenemos los fuentes del Framework, puedes ver la
implementacin de todas estas estructuras ConstructorHelpers, FObjectFinder. En
XCode basta con dar clic sobre su nombre con la tecla cmd presionada. Esto te llevar a
la declaracin de la estructura. Tomate unos minutos y dale un vistazo por arriba para
que entiendas mejor su funcionamiento. Por ltimo notar que para definirle la ruta a
FObjectFinder usamos el Macro TEXT, bsicamente todos los strings que escribamos
directo en el cdigo lo tendemos que hacer con esto, para que el compilador pueda
convertir el string al tipo de dato correcto, en este caso un TCHAR *.
Bien, pues en teora tenemos en PlayerPawnBPClass.Object la instancia de nuestro
Character (PlayerPawnBPClass es el nombre que le dimos a la variable que acabamos
de crear de tipo FObjectFinder), lo que queda es inicializar la propiedad
DefaultPawnClass con este objeto. La clase GameMode tiene la propiedad
DefaultPawnClass que define el Pawn que usar el personaje.
Listo, compila y ejecuta el juego. Al arrancar el juego automticamente se agrega al
Level nuestro personaje. Esto es porque en el Level que nos crea el Unreal Editor por
defecto con la plantilla que seleccionamos al crear el proyecto, tiene un Actor de tipo
Player Start. Y el GameMode automticamente busca en el Level si hay una instancia de
un Player Start y agrega en esa posicin el Character.
Pero que problema tenemos ahora, perdimos el control, ya no podemos desplazarnos por
la escena y la cmara est como en los ojos del personaje :(. Bien, vamos a solucionar
este asunto configurando temporalmente una cmara esttica en nuestro juego.
Configurando una cmara esttica desde C++
En el Editor da clic derecho dentro del ViewPort y selecciona del men desplegable
Place Actor/Camera. Usa las herramientas de traslacin y transformacin para apuntar la
cmara en la direccin del Play Start, para que se vea el personaje. As me qued a mi:
Ahora vamos a decirle al Unreal que la vista del juego ser desde esta cmara. Para esto
tenemos que comentar algo de teora.
Como hablamos anteriormente segn la filosofa de Unreal es el PlayerController la
interfaz entre el personaje protagnico del juego (el Character) y el ser humano. Por lo
que es lgico que lo referente a la vista del juego sea implementado aqu.
PlayerController tiene el mtodo SetViewTargetWithBlend este mtodo permite definir
en cualquier momento a donde es que est mirando la cmara del juego. Lo que
vamos a hacer es llamar a este mtodo y decirle que use la cmara que pusimos en el
Level como la cmara del juego.
Podemos cambiar la direccin a la que apunta la cmara de nuestro juego en cualquier
momento, pero en este caso queremos que desde el inicio sea la direccin a la que
apunta la cmara que agregamos al level. Para esto vamos a usar un evento muy usado
en Unreal que es el evento BeginPlay. todas las clases que hereden de AActor tienen
este mtodo que se llama, como dice su nombre, cuando inicia el juego. Vamos a
sobrescribir este mtodo en nuestro PlayerController (que deriva de AActor) para en ese
momento cambiar la cmara del juego.
Abre UE4DemoPlayerController.h y abajo del macro
GENERATED_UCLASS_BODY() agrega la siguiente lnea: virtual void BeginPlay()
override; con esto hacemos visible el mtodo BeginPlay en nuestra clase
UE4DemoPlayerController para poderlo sobrescribir en el .cpp.
Siguiendo la lgica que usamos para implementar esto desde C++. Lo primero que
hicimos fue implementar el Evento BeginPlay. Pues eso mismo haremos aqu, el
Blueprint Editor es un Editor de scripting visual, por lo que en este Editor lo que
haremos bsicamente es programar pero con grficos (si si . . . bien complejo de
asimilar y entender la primera vez :) ) aqu podemos agregar variables, eventos del
sistema, funciones de clases especificas etc. En fin, todo, o casi todo lo que haces en C+
+ lo podrs hacer en el Blueprint Editor.
Comenzaremos agregando el Evento Begin Play. Clic derecho en el centro de la pantalla
desmarca la opcin Context Sensitive y busca Event Begin Play. Acabamos de agregar a
nuestro script visual un Nodo que representa al mtodo Begin Play de nuestro juego.
Ahora, segn nuestra implementacin en C++ lo que hicimos dentro del BeginPlay fue
obtener la referencia de la cmara que tenemos en el Level y llamar al mtodo de la
clase PlayerController SetViewTargetWithBlend pasndole como parmetro la cmara.
Pues eso mismo haremos aqu.
Primero, necesitamos una referencia al PlayerController, recuerda que este script es
global a nivel del Level y la implementacin de C++ la hicimos dentro del
PlayerController.
Agrega un nuevo Nodo como ya sabes pero ahora ser Get Player Controller. Este nodo
nos retorna la referencia del Player Controller del juego. Ahora necesitamos llamar al
mtodo SetViewTargetWithBlend como mismo hicimos en C++. De nuevo agrega un
nuevo Nodo de nombre SetViewTargetWithBlend. Listo, ya tenemos todos los
elementos que necesitamos para nuestro algoritmo visual. Pero falta una cosa,
conectarlos.
El Nodo Event Begin Play tiene como un puerto que representa la salida. O sea, lo que
se va a ejecutar cuando se lance este evento en el juego y si te fijas en el nodo Set View
target with Blend tiene un puerto de entrada y de salida. El de entrada es el que nos
interesa. Da clic en el puerto del evento Event Begin Play y arrastras la flecha hasta el
puerto de entrada de Set View Target with Blend, cuando te muestre una marquita verde
sultalo. Con esto hemos hecho la conexin entre la salida de Event Begin Play y Set
View target with Blend. Que quiere decir esto, que cuando se ejecute nuestro juego, se
va a disparar el evento BeginPlay y se llamar al mtodo Set View target with Blend.
Pero que pasa, SetViewtargetWithBlend vimos que es un mtodo que pertenece al
PlayerController, por lo que hay que definirle al nodo Set View Target With Blend quien
es el Player Controller. El Nodo Get Player Controller tiene un puerto de salida que dice
Return Value y el Nodo Set View Target with Blend tiene un puerto de entrada que dice
Target. Conecta estos dos puertos y de esta forma estars diciendo que el mtodo
SetViewTargetWithBlend que se llamar es el del PlayerController retornado por el
Nodo Get Player Controller. Por ltimo recuerda que hay que pasarle como parmetro al
SetViewTargetWithBlend el Actor que usar para configurar el nuevo punto de mira.
Para esto nos falta agregar un ltimo Nodo, el nodo que representa a la cmara. Salva
estos cambios, cierra el Editor y selecciona en el Level la cmara que agregamos
anteriormente. Ahora abre de nuevo el Editor da clic derecho y vers que tienes un
acceso directo para agregar un Nodo Camera Actor. Una vez agregado conecta el puerto
de salida del camera actor al puerto New View Target del Set View Target with Blend.
Listo, ya tenemos nuestro script visual completo. En la esquina superior izquierda tienes
un botn que dice Compile. Da clic ah. Por ltimo vamos a eliminar la sobre-escritura
del mtodo Begin Play en la clase C++ ya no es necesario (si quieres comntalo para
que no pierdas el cdigo).
Cierra el Editor, abre el proyecto C++ y comenta en el UE4DemoPlayerController.h la
declaracin del mtodo BeginPlay. Ve ahora al UE4DemoPlayerController.cpp y
comenta o elimina completamente la implementacin del mtodo BeginPlay.
Listo !!. Compila y ejecuta el juego. Como notars, es idntico el resultado :). Ya te
digo, es decisin tuya implementar lo que quieras en el Blueprint Editor o en C++ tu
mismo le iras encontrando las ventajas y desventajas a cada mtodo segn lo que
quieras hacer.
Tomate unos minutos si quieres, que an nos quedan varias cosas :)
Configurando las animaciones del personaje
De momento lo que tenemos es bastante poco funcional. Simplemente al abrir el juego
vemos al personaje protagnico, ya visto desde una cmara fija pero est ah quieto sin
hacer nada y esttico totalmente. Vamos a darle un poco de vida.
Recuerda que nuestro equipo de diseo :) nos entreg adems del modelo con su
esqueleto en FBX las animaciones de caminar y reposo, tambin en formato FBX listas
para importarlas. Pues vamos a ello. Abre el Editor en el Content Browser crea una
nueva carpeta, yo le pondr Animations. Aqu tendremos todas las animaciones que
importemos. Entra a la carpeta e importa los dos FBX Walk.FBX e Idle.FBX. Al
seleccionarlas vers que por defecto en la ventana de FBX Import sale seleccionado
Animations. Ms abajo tiene la opcin que permite ya en el momento de la importacin
seleccionar el esqueleto al que estn asociadas estas animaciones. Da clic ah y
selecciona Hero_Skeleton por ltimo da clic en Import.
Ya tenemos las animaciones de nuestro personaje, si quieres puedes darle doble clic
desde el Content Browser para abrirlas en el Persona Editor y ver un preview de las
mismas. Desde este Editor puedes ver todos los detalles de la animacin, reproducirla y
muchas ms cosas que veremos en prximos tutoriales.
Ya tenemos las animaciones ahora falta asociarlas con nuestro personaje.
Creando la maquina de estado y actualizando el Character con la animacin de
reposo y caminar mediante Animation Blueprints.
El Animation Blueprints es la herramienta que nos da Unreal Engine para implementar
toda la lgica de las animaciones del personaje de una forma sper simple. A partir de
mquinas de estado y visual scripting.
Vamos a crear entonces el Animation Blueprints para el personaje. Entra en el Content
Browser a la carpeta Animations da clic derecho en un espacio vaco y selecciona
Animation/Animation Blueprint. Esto te abrir la ventana de creacin de Animation
Blueprint. En la seccin Parent Class selecciona AnimInstance y abajo en la seccin
Target Skeleton escribe el nombre del esqueleto que usa nuestro hroe, Hero_Skeleton
por ltimo da clic en el botn OK.
Seguidamente vamos a definir la lgica del estado Idle. Dale doble clic al nodo Idle, da
clic derecho y selecciona Animation/Play Idle. Esto agrega el Nodo que representa la
animacin Idle que importamos anteriormente al Editor. ahora conecta este nodo al
nodo Final Animation Pose.
Listo vamos a repasar como quedan todas las conexiones en cada uno de los niveles. De
adentro hacia afuera tenemos nodo Play Idle conectado al nodo Final Animation Pose.
En el nivel superior (HeroStateMachine) nodo Entry conectado al nodo Idle y por
ltimo en el nivel superior (AnimGraph) el nodo HeroStateMachine conectado al nodo
Final Animation Pose. Hecho esto da clic en Compile en la esquina superior derecha del
Editor. En el panel de la izquierda podremos ver un preview ya del personaje con la
animacin del Idle
Lo que acabamos de hacer es definir los controles que tendr nuestro juego. El nombre
que le ponemos, por ejemplo, MoveForward es para conocer esta entrada desde
programacin y lo que seleccionamos en el combobox es el control que disparar esta
accin. El valor de Scale es un valor numrico que llega al mtodo desde programacin,
generalmente con -1 y 1 es suficiente para la mayora de los casos. Otra cosa a notar es
que gracias a este valor de Scale las acciones que son en una direccin y en su contraria
las registramos con el mismo identificador (MoveForward por ejemplo) y simplemente
le cambiamos el valor de Scale a 1 si es hacia delante y a -1 si es hacia atrs.
Bien, hecho esto vamos a programar en el Character la lgica para lograr el
desplazamiento por la escena con estos controles, de momento ser muy simple el
desplazamiento del personaje en la escena, pero suficiente para entender como funciona
todo. Bsicamente necesitamos dos cosas. Sobrescribir el mtodo virtual void
SetupPlayerInputComponent(class UInputComponent* InputComponent) de APawn
para registrar los mtodos que se van a llamar cada vez que se detecte las entradas que
definimos, o sea un MoveForward o un MoveRight, y por supuesto, implementar el
desplazamiento del Character en dependencia de la entrada.
Abre la clase HeroCharacter.h y modifcala para que quede de la siguiente forma:
1 UCLASS()
AHeroCharacter : public ACharacter
2 class
{
3
4
5
6
7
8
9
1
GENERATED_UCLASS_BODY()
0
11
1 protected:
2
/**
1
* Se llama cuando el motor detecta la entrada configurada para
3 'MoveForward'.
1
* En este caso cuando el usuario toca la tecla W o S del
4 teclado
*/
1
void MoveForward(float Value);
5
1
/**
6
* Se llama cuando el motor detecta la entrada configurada para
1 'MoveRight'.
* En este caso cuando el usuario toca la tecla A o D del
7
teclado
1
*/
8
void MoveRight(float Value);
1
9
/**
* Metodo de la clase APawn que permite configurar los 'binding'
2
de
los
controles
0
* Es llamado automaticamente por el Engine
2
*/
1
virtual void SetupPlayerInputComponent(class UInputComponent*
2 InputComponent) OVERRIDE;
2
2 };
3
2
4
2
5
2
6
Nada raro aqu, simplemente definimos dos mtodos en donde vamos a implementar la
lgica para cuando se detecte cada entrada y agregamos aqu tambin la declaracin del
mtodo SetupPlayerInputComponent de APawn para poderlo sobrescribirlo
Ahora pasa al HeroCharacter.cpp y modifcalo para que quede de la siguiente forma:
1 #include "UE4Demo.h"
2 #include "HeroCharacter.h"
3
4 AHeroCharacter::AHeroCharacter(const class
5 FPostConstructInitializeProperties& PCIP)
6 :{ Super(PCIP)
7
8 }
9
1 void AHeroCharacter::SetupPlayerInputComponent(class UInputComponent*
0 InputComponent)
11{
//Le dice al motor que cuando detecte las entrada de tipo
1 MoveForward que llame al metodo AHeroCharacter::MoveForward
2
InputComponent->BindAxis("MoveForward", this,
1 &AHeroCharacter::MoveForward);
3
//Le dice al motor que cuando detecte las entrada de tipo
1
MoveRight
que llame al metodo AHeroCharacter::MoveRight
4
InputComponent->BindAxis("MoveRight", this,
1 &AHeroCharacter::MoveRight);
5 }
1
6
1 /**
7 * Se llama cuando se detecta la entrada de tipo MoveForward
1 (Cuando el usuario toca las teclas W o S).
* Determina la direccin en la que est el personaje y le aplica
8 un movimiento (positivo o negativo) en esa direccin
1 *
9 * @param Value Value es igual a 1 cuando se detecta W y -1 cuando
2 se detecta S
*/
0 void AHeroCharacter::MoveForward(float Value)
2 {
1
if ((Controller != NULL) && (Value != 0.0f))
{
2
//Obtiene la rotacion actual
2
const FRotator Rotation = Controller->GetControlRotation();
2
3
// Crea el vector de direccion a partir de hacia donde est
2 rotado y aplica el movimiento
const FVector Direction =
4
2 FRotationMatrix(Rotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
5
}
2 }
6
2 /**
7 * Se llama cuando se detecta la entrada de tipo MoveForward
2 (Cuando el usuario toca las teclas A o D).
@param Value Value es igual a 1 cuando se detecta D y -1 cuando
8 se* detecta
A
2 */
9 void AHeroCharacter::MoveRight(float Value)
3 {
if ( (Controller != NULL) && (Value != 0.0f) )
0
{
3
//Determina la direccin del movimiento hacia los lados.
1 Notar que solo nos intereza la rotacion en el eje Y
const FRotator Rotation = Controller->GetControlRotation();
3
const FRotator YawRotation(0, Rotation.Yaw, 0);
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
// Crea el vector de la direccin y aplica el movimiento
4
const FVector Direction =
4
FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
4
AddMovementInput(Direction, Value);
5
}
4 }
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
Ya aqu si tenemos algunas cosas que comentar, pero de seguro a estas alturas tienes una
idea de todo. Primero, en SetupPlayerInputComponent usamos el parmetro que recibe
para configurar los mtodos que se van a llamar cuando se detecte cada una de las
entradas. O sea, con InputComponent->BindAxis(MoveForward, this,
&AHeroCharacter::MoveForward); estamos diciendo que cuando el Engine detecte la
entrada MoveForward, que como definimos ser cuando el jugador presione las teclas
W o S del teclado, se llamar el mtodo MoveForward. Y el mismo principio para la
otra entrada.
Ahora vamos a ver la implementacin del mtodo MoveForward. Este mtodo recibe un
parmetro float, que es el valor Scale que registramos en el Editor. O sea, cuando se
toque la W se llamar este mtodo con 1.0 como parmetro y cuando se toque la S se
llamar este mtodo con -1.0 como Value.
El mtodo lo que hace es determinar la rotacin que tiene el modelo y el vector en la
direccin a la que est orientado y mediante el mtodo AddMovementInput hacemos
que el personaje se mueva en esa direccin. Fjate que se calcula la direccin y como se
pasa Value que ser 1 o -1 entonces el personaje se mover hacia delante o hacia atrs
segn Value. AddMovementInput es un mtodo de APawn que permite aplicar un
movimiento al Pawn para el caso del personaje que no sea un cuerpo fsico, como el
nuestro. Para el caso del MoveRight fjate que es prcticamente lo mismo, pero al
calcular la direccin lo hacemos en base al eje Y y no con el eje X.
Listo, esto es todo lo que necesitamos. Compila y ejecuta el juego, cuando abra presiona
las teclas W,S,A,D del teclado para controlar al personaje. Cuidado no te caigas por los
bordes de la plataforma ;)
Umm pero an tenemos dos problemas con este movimiento. Primero, el personaje no
rota en la direccin del movimiento como sera lo lgico, y el otro problema es que a
pesar que se est moviendo sigue con su animacin de reposo. Vamos entonces a
solucionar estos dos problemas.
Primero, para solucionar el problema de la rotacin es muy fcil. Abre el
HeroCharacter.cpp y modifica el constructor para que te quede de la siguiente forma:
1 AHeroCharacter::AHeroCharacter(const class
PCIP)
2 FPostConstructInitializeProperties&
: Super(PCIP)
3 {
4
//Por defecto esta propiedad viene en true para el Character.
//Pero en nuestro modelo de desplazamiento, no queremos que el
5
6 personaje rote en base a la rotacin del Controller.
bUseControllerRotationYaw = false;
7
8
//Configuracin del componente CharacterMovement
9
1
//Al estar en true habilita para que el character se rote en la
0 direccin del movimiento al comenzar el movimiento.
CharacterMovement->bOrientRotationToMovement = true;
11
1
//Factor de rotacin para la propiedad anterior.
2
CharacterMovement->RotationRate = FRotator(0.0f, 540.0f, 0.0f);
1
3
1
4
1
//Bajamos un poco el valor por defecto de MaxWalkSpeed para que
5
el personaje camine un poco ms lento.
1
CharacterMovement->MaxWalkSpeed = 400.0f;
6 }
1
7
1
8
Recuerdas que el Character tiene un CharacterMovement verdad ?. Pues aqu lo que
hicimos fue modificar algunos valores para cambiar el comportamiento por defecto del
Character. Puedes revisar en el Editor desde el HeroCharacterBlueprint que creamos,
todas las propiedades que tiene el CharacterMovement, juega un poco con ellas para que
veas todo lo que se le puede definir al movimiento del Character.
Agregando animacin de caminando al personaje.
Vamos a trabajar ahora en el ltimo problema que tenemos, hacer que el personaje
cuando est en reposo tenga su animacin de reposo (como ahora) pero cuando est
caminando reproduzca la animacin de caminando, con esto veremos uno de los
mecanismos que nos da Unreal Engine para ligar dos animaciones de forma suavizada,
los Blend Space.
Blend Space es el Nodo del Animation Blueprint que nos permite hacer blending entre
dos animaciones en base a la entrada de valores. En Unreal tenemos dos tipos: el Blend
Space que es para varias entradas y el Blend Space 1D que es para una sola entrada.
Para este simple ejemplo usaremos el Blend Space 1D ya que necesitamos solamente
una entrada para lo que queremos lograr, la velocidad del desplazamiento.
Abre el Editor y dentro de la carpeta Animations en el Content Browser da clic derecho
para agregar un Animations/BlendSpace1D ponle de nombre IdleWalkBlendSpace1D y
dale doble clic para abrir el Editor de este elemento. Aqu la idea es la siguiente, decirle
las dos animaciones que harn blending segn el valor de una variable. En el panel de
las propiedades de IdleWalkBlendSpace1D en el X Axis Label escribe Speed (esto es
solo para una referencia, puede ser cualquier otra palabra que te represente el valor que
se tiene en cuenta para cambiar entre una animacin y otra). En el rango pon 0 y 100.
Ahora fjate que ms abajo tienes un espacio como de una grfica, arrastra hacia ah
desde el panel Asset Browser la animacin Idle y colcala al inicio del eje X, has lo
mismo para la animacin Walk y colcala al final del eje. Listo, ahora mueve el cursor
sobre el eje y fjate en el panel Preview como a medida que el valor se va modificando,
el modelo va cambiando de su estado Idle a Walk. Guarda y cierra este editor.
est detenido se anima con su animacin de reposo y cuando est caminando se anima
con su animacin de Walk. :)
Conclusin
Hasta aqu este tutorial de introduccin al Unreal Engine 4. En el prximo tutorial
modificaremos la cmara, los controles y el movimiento del personaje para hacer
nuestro juego un side-scroller style. Adems agregaremos algo de lgica al juego,
nuestro personaje tendr que lograr alcanzar todas las monedas que existan en el
escenario antes de un tiempo determinado, para poder ganar, de lo contrario perder y
tendr que comenzar de nuevo. Mientras djame saber tus comentarios :).
Abre el editor con el proyecto UE4Demo que dejamos del tutorial pasado y comienza
eliminando los objetos visibles que no vamos a usar (las sillas, la mesa, la estatua). Solo
qudate con un objeto floor (piso). Despus, modifica el objeto floor usando las
herramienta de transformacin y escalado que tienes en la esquina superior derecha del
viewport. Despus crea copias del objeto, modifcalas indistintamente y reprtelas por el
nivel. Asegrate de dejar el Actor Play Start sobre una de las plataformas, para evitar
que el personaje cuando inicie el juego se caiga al abismo. En mi caso qued as ( tu
puedes usar mucho ms tu imaginacin para lograr algo mejor :) ):
Como notars, al lanzar el juego se muestra un cartel rojo que dice: LIGHTING NEEDS
TO BE REBUILT. Esto pasa porque modificamos la geometra en el nivel y el motor
necesita recompilar las luces para que la iluminacin se adapte a la nueva geometra.
Puedes recompilar las luces desde el Toolbar/Build/Build Lighting Only. Vuelve a
correr, vers que todo volvi a la normalidad.
Bien, ya tenemos un nivel, muy bsico, pero suficiente para implementar y probar todo
lo que haremos hoy.
Configurando la cmara para un juego side-scroller.
En este punto es importante aclarar que el UE4 ya trae por defecto una plantilla para
comenzar un juego de este estilo. De hecho usaremos prcticamente lo mismo que usa
esta plantilla, pero la idea es hacerlo desde cero en este tutorial para entender bien el
porqu de las cosas.
Lo primero ser cambiar la cmara del juego. En el tutorial pasado configuramos una
cmara fija bastante simple, pero que nos sirvi para introducirnos tanto en el visual
scripting con el Blueprint Editor, como en la programacin en C++. Ya no usaremos
ms esta cmara. Elimina la actual implementacin que tengas de la cmara, si te
quedaste con la solucin por cdigo, comenta el cdigo dentro del Begin Play y si te
quedaste con la solucin en el Blueprint, elimina todos los nodos o elimina la conexin
que sale del Nodo BeginPlay con esto ltimo se rompe el algoritmo porque no continua
la ejecucin al lanzarse el BeginPlay pero se mantienen el resto de las conexiones y los
nodos en el Editor, por si los quieres para una referencia.
Vamos ahora a configurar un nuevo estilo de cmara. Esta vez lo haremos desde C++ en
nuestra clase Character. Siempre ten en cuenta que esto mismo lo puedes hacer tambin
desde el Editor, recuerda que tenemos una clase Blueprint que hereda de nuestra clase
HeroCharacter.h. Una buena prctica sera que te aventuraras una vez que veas lo que
haremos aqu, ha hacerlo por tu parte desde el Editor modificando el
HeroCharacterBlueprint.
Abre la clase HeroCharacter.h y y despus del macro
GENERATED_UCLASS_BODY() agrega las siguientes declaraciones:
/** Brazo para apoyar fijar la cmara al Character al estilo side-
1scroller */
2UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
3TSubobjectPtr<USpringArmComponent> SpringArm;
4
5/** Cmara del juego, es adjuntada al socket del brazo para lograr el
6estilo de cmara de un side-scroller */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
7TSubobjectPtr<UCameraComponent> SideViewCamera;
Lo que hicimos fue agregar dos variables de clase a nuestro HeroCharacter, pero lo que
seguro ms te llama la atencin es el macro UPROPERTY que incluimos en cada
variable. Este macro nos sirve para definir si queremos reflejar o no estos atributos en el
Editor y que opciones tendremos. Como vimos, una de las cosas geniales que tiene
Unreal Engine 4 es la perfecta integracin entre el cdigo y el editor y este macro es
uno de los que nos ayuda a esto. A este macro le podemos pasar parmetros:
VisibleAnywhere: Indica que esta propiedad ser visible en el panel de propiedades en
el Editor.
BlueprintReadOnly: Esta propiedad podr ser leda desde un VisualScript en el
Blueprint, pero no modificada.
Category: Permite especificar un nombre de categora bajo la que se mostrar esta
propiedad en el Editor
Puedes ver una referencia detallada de todos los parmetros que podemos indicar para
UPROPERTY en la clase Runtime/CoreUObject/Public/UObject/ObjectBase.h dentro
del namespace UP. Para llegar rpido puedes dar clic sobre cualquiera de las categoras
con la tecla cmd presionada.
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
Prstale atencin a los comentarios de cada lnea para que puedas entender cada una lo
que hace. En general creamos y configuramos el objeto USpringArmComponent y lo
agregamos al Character. Despus, creamos y configuramos el objeto
UCameraComponent (la cmara del juego) y la agregamos al USpringArmComponent
para fijarla a una distancia del Character. Notar que las instancias de los objetos las
creamos a partir del objeto que recibimos en el constructor, con
PCIP.CreateDefaultSubobject.
Listo, compila y ejecuta el juego, ahora tendrs la vista del juego al estilo sidescroller :). Puedes probar moverte por el nivel que creaste para que veas como la cmara
te sigue en todo momento a la misma distancia.
Nueva configuracin de los controles del juego. Ahora con un control para saltar
Como puedes notar, el mtodo qued mucho mas simple que la anterior implementacin
y ya debes entender sin problema que hace. Simplemente aplicamos un vector de
movimiento que afecta solo un eje con el valor de Value. Por lo que cuando el usuario
toque la tecla D el personaje se mover hacia delante (hacia la derecha) y cuando toque
la tecla A se mover hacia la izquierda.
Ahora tenemos que implementar el mtodo que se llamar cuando el usuario toque la
barra espaciadora para hacer saltar a nuestro hroe. Muy simple, agrega la siguiente
lnea al mtodo SetupPlayerInputComponent:
InputComponent->BindAction("Jump", IE_Pressed, this,
1&ACharacter::Jump);
Nueva maquina de estado del Character, con los estados del salto
Lo que acabamos de hacer aqu es definirle 3 nuevos estados que va a tener el personaje
y el orden en los que podr alcanzarlos. O sea, el personaje podr estar en
reposo/corriendo y de ah puede pasar al estado de JumpStart (inicio del salto). De este
estado JumpStart puede pasar a JumpLoop (ciclo del salto). De JumpLoop puede pasar a
JumpEnd y por ultimo de JumpEnd regresa nuevamente a Indle/Walk. Fjate que la
conexin entre los estados es una flecha que marca la direccin, o sea, cual es el estado
origen y cual el destino. Es bastante lgico, si lo imaginas un poco o te paras un
segundito y saltas :) reproducirs estos 4 estados y la transicin por cada uno de ellos.
Cada estado tiene sobre la flecha de la transicin un iconito arriba que representa la
condicin que define cuando es que se pasar de un estado a otro. Esta condicin las
tenemos que programar nosotros (mediante visual scripting con el Blueprint Editor). El
primer caso sera cuando el personaje est en Idle/Walk y comienza un salto. La
condicin que usaremos para pasar a este estado ser muy simple, crearemos una
variable BOOL que nos permita saber si el personaje est en el aire o no y si est en el
aire va a pasar al estado de JumpStart.
Da doble clic en el icono de la transicin y entrars en el modo edicin de este
elemento, Por defecto tiene un nodo Result con la descripcin Can Enter Transition, este
nodo espera un parmetro bool (true/false) que le dir si la maquina activa este estado o
no, o sea, si se efecta la transicin. Crea y agrega una nueva variable, como mismo
hicimos en el tutorial pasado, pero de tipo bool. Ponle de nombre IsInAir o cualquier
otro nombre identificativo que se te ocurra. Agrgala al blueprint en modo GET. Ahora
conecta el Puerto de salida de esta variable al Puerto de entrada del nodo Result.
Lo que acabamos de programar aqu mediante visualscripting es: Si la variable IsInAir
tiene valor true, entonces ejecuta esta transicin. Lo que quiere decir, que el personaje
pasara para el estado de JumpStart.
Ahora, cual sera la condicin para pasar de JumpStart a JumpLoop?. Pues para este
caso usaremos un nuevo nodo. La idea es determinar cuando se est a punto de acabar la
animacin de JumpStart para comenzar a reproducir el loop. Da doble clic en el icono
de transicin de JumpStart a JumpLoop, como ya vimos, por defecto tenemos el nodo
Result al que hay que conectar la salida del algoritmo que preparemos aqu y que
determina cuando pasa el personaje a este estado. Agrega un nuevo nodo de tipo Time
Remaining (Ratio) para Jump_Start Asset. Este nodo nos permite tener en todo
momento el tiempo que le va quedando a la animacin para que termine. Agrega ahora
otro nodo de tipo float < float. Este nodo es un mtodo que nos permite saber si un
parmetro A es menor que otro B. Conecta el puerto de salida del nodo TimeRemaining
al puerto de arriba de entrada de la comparacin. Para el segundo parmetro de la
funcin menor que, no vamos a conectar ningn nuevo nodo, sino que vamos a definir
un valor a mano. Como queremos que se comiese a reproducir Jumploop ya cuando est
a punto de terminar el JumpStart, pon en el campo del segundo parmetro de la funcin
menor que: 0.1. Por ultimo conecta la salida de esta funcione al nodo Result. Te
quedara de la siguiente forma:
Bsicamente lo que programamos aqu fue: Si el tiempo que le queda a la animacin por
terminar es menor que 0.1 pasa al siguiente estado.
Guarda estos cambios, sale del modo de edicin de la transicin y entra en el modo
edicin del estado JumpLoop, agrega la animacin JumpLoop y conctala al Result. A
diferencia de JumpStart, JumpLoop si queremos que se reproduzca en ciclo ya que es
esta la animacin que se estar reproduciendo mientras el personaje est en el aire. Para
esto, rectifica que en el panel de propiedades del nodo que representa la animacin,
tenga marcado el atributo Loop.
Sale del modo de edicin de esta transicin, entra en el modo de edicin del estado
JumpEnd. Arrastra y conecta al Final Pose la animacin JumpEnd y desmrcale la
opcin de loop.
Por ltimo, tenemos que definir las condiciones para pasar de JumpEnd de nuevo a
Idle/Walk. JumpEnd es la animacin que termina el salto, ya el personaje est en el
suelo pero recuperndose de la cada. Por lo que simplemente para pasar a Idle/Walk es
esperar a que la animacin JumpEnd est a punto de terminar, como mismo hicimos de
JumpStart a JumpLoop. Esto lo debes poder hacer por tu cuenta, as que intntalo ;)
te tiene que quedar as:
Muy bien, ya tenemos la maquina de estado del personaje lista, pero nos falta una cosa.
En estos estados estamos usando una nueva variable IsInAir solo en modo GET, pero
recuerda, como mismo hicimos para Speed, en algn punto esta variable tiene que tomar
valor.
Cierra el AnimGraph y abre el EventGraph, agrega un nodo GetMovementComponent
conecta el puerto de salida del TryGetPawnOwner que tenemos desde el tutorial pasado
al puerto de entrada del GetMovementComponent, agrega otro nuevo nodo Is Falling,
conecta el puerto de salida de GetMovementComponent al de entrada del IsFalling.
Agrega la variable IsInAir en modo SET y conecta la salida de IsFalling a la entrada de
IsInAir. Por ltimo, conecta el puerto blanco de salida de SET Speed al de entrada de
SET IsInAir para la continuidad del algoritmo.
Si has llegado hasta aqu desde el tutorial anterior no debes tener problema en entender
que acabamos de hacer aqu. Obtenemos el MovementComponent del Character, este
tiene un mtodo IsFalling que retorna true/false si el usuario est en el aire o no. Este
mtodo es el que usamos para settear el valor de la variable IsInAir.
Listo, ya tenemos la nueva maquina de estado para nuestro personaje. Compila el
AnimationBlueprint y corre el juego. Toca la barra espaciadora . . . ya nuestro hroe
sabe saltar tambin :)
Fjate en un detalle, estamos llamando al BindAction para la entrada Run dos veces y
pasndole el mismo mtodo ToggleRunState (que vamos a implementar ahora) pero la
diferencia entre uno y otro es que el segundo parmetro especifica exactamente cuando
es que se va a llamar al mtodo. IE_Pressed cuando se presione la tecla Shift y
IE_Release cuando se suelte. Lo que queremos hacer es que si se est tocando el shift el
personaje corre pero si se suelta deja de correr, algo parecido a la misma combinacin
que tenamos que hacer en el Super Mario para el super salto !! :)
Bien, ahora vamos a implementar el mtodo ToggleRunState. Agrega en el .h de
HeroCharacyer.cpp la declaracin del mtodo:
1 /**
2 * Se llama cuando el motor detecta la entrada Run
3 * Intercambia el estado de correr del personaje
4 */
void ToggleRunState();
5
Pasa a la .cpp y agrega la implementacin:
1
2
3 /**
* Se llama cuando el motor detecta la entrada Run
4 * Intercambia el estado de correr del personaje
5 */
6 void AHeroCharacter::ToggleRunState()
7 {
//Si el atributo MaxWalkSpeed del CharacterMovement est en 400.f lo
8 aumentamos a 900.f para que el personaje se mueva mas rpido
9 //De lo contrario lo volvemos a poner en 400.f para que regrese a su
1 velocidad de caminar.
if(CharacterMovement->MaxWalkSpeed == 400.0f)
0
CharacterMovement->MaxWalkSpeed = 900.0f;
11
else
1
CharacterMovement->MaxWalkSpeed = 400.0f;
2 }
1
3
Muy simple, por defecto la velocidad de desplazamiento del personaje es 400 cuando
este mtodo se llama por primera vez (cuando se presiona shift) el MaxWalkSpeed est
en 400 y se pasa a 900, lo que har que el personaje de desplace ms rpido, y cuando
se suelte el Shift se va a llamar de nuevo y se volver a poner la velocidad en 400,
disminuyendo el desplazamiento.
Una buena tarea sera que intentes implementar este mecanismo de correr en el
Blueprint del HeroCharacter. Recuerda eliminar el cdigo C++ si vas a usar el
Blueprint. Te tendra que quedar as:
Yo en lo personal prefiero siempre mantener toda la lgica del personaje desde C++,
pero ese ejercicio te puede servir para tomar ms soltura en el Blueprint Editor que tanto
llama la atencin :).
Otra buena tarea sera que expongas los valores mximo y mnimo del MaxWalkSpeed
en el Editor para que se puedan modificar fcilmente sin necesidad de llegar al cdigo.
Ya sabes que tendrs que usar el macro UPROPERTY, pero este si no te dir como tiene
que quedarte, intntalo por tu cuenta ;)
Compila y ejecuta el juego, a medida que estas caminando deja presionada la tecla shift,
vers que el personaje se desplaza mucho ms rpido, pero muy feo ya que solamente se
afecta su desplazamiento pero no se afecta la animacin.
Agregando la animacin de correr al personaje
Vamos a agregar otra animacin a nuestro personaje, para que cuando est corriendo se
anime correctamente. Para esto usaremos el mismo estado Idle/Walk que ya tenemos,
pero ms aun, dentro de este usaremos el mismo nodo con el que hacemos blend entre
las animaciones de Idle y Walk. Una sper potencialidad de este nodo es que podemos
agregarle ms animaciones, no solo dos. La idea ser modificarlo para configurarle tres
puntos de control y no dos como tenemos ahora. Uno al inicio del grafico con la
animacin Idle, otro en el medio con la animacin Walk y otro al final con la animacin
Run.
Importa Run.FBX de los recursos para el proyecto. Abre el IdleWalkBlendSpace1D que
creamos en el tutorial pasado. Cmbiale la propiedad X Axis Range a 900 (que es el
valor que toma el personaje al correr) y da clic en el botn Apply Parameter Changes.
2
0
2
1
Prstale atencin a los comentarios, como siempre, para los detalles. Bsicamente lo
que hacemos es definir el atributo BaseCollisionComponent que ser el componente
raz de nuestra moneda, y el que usaremos para detectar colisiones con ella y un
UStaticMeshComponent para poder definir el StaticMesh que representar a la moneda
en el nivel. Por ltimo el atributo bIsActive que lo usaremos como bandera para
desactivar la moneda cuando se colisione con ella.
Pasa ahora a la .cpp y modifica el constructor para que te quede as:
1
2
3
4 ACoin::ACoin(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
5
{
6
//Crea la instancia del USphereComponent
7
BaseCollisionComponent =
8 PCIP.CreateDefaultSubobject<USphereComponent>(this,
9 TEXT("BaseSphereComponent"));
1
//Inicializa el RootComponent de este Actor con el
0
USphereComponent
11
RootComponent = BaseCollisionComponent;
1
2
//Crea la instancia del UStaticMeshComponent
1
CoinMesh =
3 PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this,
1 TEXT("CoinMesh"));
4
//Agregamos el UStaticMeshComponent como hijo del root component
1
CoinMesh->AttachTo(RootComponent);
5
1
//Por defecto la moneda estar activa
6
bIsActive = true;
1 }
7
1
8
Aqu instanciamos un USphereComponent como el RootComponent de nuestra
moneda. Creamos la instancia del UStaticMeshComponent que despus configuraremos
desde el editor, lo agregamos al RootComponent y por ultimo inicializamos en true
bIsActive. Ya que queremos que por defecto la moneda est active.
Agregando monedas al nivel
Vamos ahora a crear el Blueprint de la moneda, como mismo hicimos con el Character
en el tutorial pasado. Entra en la carpeta Coin del ContentBrowser, clic
derecho/Blueprint y selecciona Coin como clase base, dale de nombre CoinBlueprint.
Como vers, est compuesta por los mismos componentes que definimos en el
constructor un USphereComponent como RootComponent y un
UStaticMeshComponent. Despliega el CoinMesh y selecciona el StaticMesh que
importamos para la moneda.
Ya tenemos las monedas en el nivel, pero poco se puede hacer con eso. Si caminas hacia
las monedas no pasa nada, adems las monedas se ven muy feas estticas ah sin
moverse. Vamos a arreglar estas cosas
Simple mecanismo de colisin para recolectar las monedas
Como pudiste ver, ahora mismo cuando el personaje le pasa por arriba a la moneda no
pasa nada, esta sigue ah como si nada pasara. Vamos a solucionar esto implementado la
lgica para detectar cuando el personaje est sobre una moneda. De momento ser
simple nuestra implementacin, lo que haremos es incrementar un valor de Monedas
recolectadas que tendremos en nuestro personaje y llamar al mtodo OnCollected() que
crearemos en la moneda para ponerla inactiva, eliminarla del nivel y temporalmente
imprimir un log en la pantalla para debuguear este mecanismo.
Abre la clase HeroCharacter.h ya agrega las declaraciones siguientes:
1
2 /** Cantidad de monedas recolectadas por el personaje */
3 UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Coins)
int32 CoinsCollected;
4
5 /** Se llama constantemente en el Tick del personaje para determinar
6 si se est colisionando con una moneda */
7 void CollectCoins();
8
9 /**
ejecuta automticamente por el Engine en cada frame del juego
1 ** Se
@param DeltaSeconds la diferencia en segundos entre el frame
0 pasado y el actual
11 */
1 virtual void Tick(float DeltaSeconds) OVERRIDE;
2
Vamos a comentar un poco cual es la lgica que usaremos. Todos los Actors en Unreal
Engine cuentan con el mtodo Tick. Este mtodo es llamado automticamente por el
Engine en cada frame del juego. Si estas leyendo este tutorial probablemente ests
familiarizado con el desarrollo de juegos, al menos la teora, y sabrs de sobra la
importancia de este mtodo. Bsicamente, todos lo algoritmos que queramos que estn
en constante ejecucin por un Actor determinado tiene que ir dentro de este mtodo
Tick. El mtodo Tick recibe como parmetro un float que es la variacin en segundos
entre un frame y el anterior. Ese valor es de mucha ayuda para usarlo como
multiplicador a la hora de modificar la posicin del personaje o la rotacin del actor
para que esto se ejecute dependiente del framerate que tenga el juego en ese momento y
evitar saltos en caso que la ejecucin del juego baje el framerate.
Entonces, lo que haremos ser tener en constante ejecucin el mtodo CollectCoins y
este mtodo lo que har ser determinar si hay alguna moneda dentro del
CapsuleComponent del Character, si es as, es que est arriba de una moneda.
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
Este mtodo como dijimos es el que se llamar constantemente en el Tick del player
para determinar si se est colisionando con una moneda o no. Tomate unos minutos en
los comentarios de cada lnea para que entiendas bien el proceso para determinar si se
est colisionando con una moneda. Este es un mecanismo de colisin muy simple, pero
suficiente para nuestro primer trabajo con colisiones en Unreal Engine 4 :). Una cosa
importante, dentro del mtodo CollectCoins hacemos referencia a la clase ACoin que
creamos, para que esto no de error ve a la parte de arriba del .ccp y debajo de la lnea
#include UE4Demo.h agrega #include Coin.h
Solo nos falta un detallito. Si intentas compilar ahora tendrs un error, porque cuando se
detecta una colisin se llama al mtodo OnCollected de la clase Coin, y nosotros nunca
hemos implementado ese mtodo. Pues bien, vamos a implementarlo.
Agrega la declaracin del mtodo en el Coin.h y la implementacin en el .cpp
1
2
3
4
5
6
7
8
9
1
0
11
1 }
2
Destroy();
Juego corriendo con el comando show COLLISION para debuguear los componentes
para colisiones de cada Actor.
Modificando el radio del componente esfera para ajustar las colisiones con la moneda
Puedes hacer lo mismo para la capsula del personaje si la quieres modificar un poco.
Vale aclarar tambin que estos valores los puedes definir desde un inicio en el
constructor de la clase desde C++. Prueba hacerlo como ejercicio.
Haciendo rotar las monedas constantemente en su eje.
Para darle un poco de vida a las monedas en el escenario, vamos a hacer que estas estn
rotando siempre en el eje Y. Cierra el editor, abre la clase Coin.h y agrega las siguientes
declaraciones:
1/** Factor de rotacion de la moneda */
2UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rotation")
3FRotator RotationRate;
4
5virtual void Tick(float DeltaTime) OVERRIDE;
Simplemente agregamos la declaracin del mtodo Tick que ya conocemos y un atributo
nuevo RotationRate de tipo FRotator. Este ser el vector de rotacin que usaremos para
aplicarle a la moneda constantemente en cada Tick. En realidad no tenemos que declarar
un atributo para esto, pero hacindalo as y sobre todo gracias al macro UPROPERTY
podemos dejar en configuracin desde el Editor el factor de rotacin que tendr la
moneda.
Pasa ahora a Coin.cpp, antes de terminar la implementacin del constructor agrega estas
dos lnea
1//Inicializa el factor de rotacion de la moneda en cada update
2RotationRate = FRotator(0.0f, 180.0f, 0.0f);
3//Activa para que se llame el mtodo Tick de este actor en cada
4update del juego
5PrimaryActorTick.bCanEverTick = true;
La primera es la inicializacin del vector de rotacin que usaremos en el Tick de la
moneda y la segunda lnea es muy importante. Por defecto en esta clase que hereda de
Actor el mtodo Tick NO se llama, para activar en el Engine que se llame el tick de esta
clase es necesario inicializar el atributo PrimaryActorTick.bCanEverTick en true.
Bien, hecho esto agrega la implementacin del mtodo Tick
1void ACoin::Tick(float DeltaTime)
2{
Super::Tick(DeltaTime);
3
4
//Agrega una rotacion a la moneda en cada tick para tenerlas
5rotando constantemente en el escenario
6
AddActorLocalRotation(this->RotationRate * DeltaTime, false);
7}
Como vez, muy simple. Bsicamente en cada update de este Actor lo que hacemos es
agregar un factor de rotacin gracias al mtodo AddActorLocalRotation.
Listo !! compila ejecuta y prueba. Super verdad !! ya las monedas se encuentran rotando
en el escenario esperando a que las atrapes :)
Implementando la lgica de la moneda mediante el Blueprint.
Como ya hemos dicho, la decisin de implementar una determinada cosa en C++ o
mediante VisualScripting con el Blueprint Editor es de uno. Yo en lo personal sigo las
dos siguientes premisas. Primero, implementar algo siempre en un solo lugar. O sea, no
tener parte de la lgica de un Actor en C++ y otra parte en el Blueprint. Trato de tenerlo
todo en un mismo lugar en la medida de lo posible. Segundo, si la lgica a implementar
en determinado Actor es muy simple, como es el caso de esta moneda, pues la solucin
ideal es el Blueprint. Independientemente de que con el Blueprint se pueden
implementar algoritmos sper largos y muy complejos, yo para estos casos prefiero usar
C++.
Una buen ejercicio es que intentes implementar la lgica de la moneda, todo lo que
hemos hecho aqu pero en Blueprint. Te tiene que quedar as:
VisualScript de la moneda
7
bIsActive = false;
8
9
//Y con el mtodo Destroy de Actor, eliminamos la moneda del
1 escenario
Destroy();
0
}
11
Lo bueno con esto es que desde C++ tenemos una implementacin, pero si queremos
podemos sobreescribir esta implementacin desde el Blueprint super verdad !??
cosas del UE4 :). Prueba esto implementado toda la lgica de la moneda desde
VisualScripting, para que vayas ganando soltura con el Blueprint Editor, una de las
maravillas del Unreal Engine 4.
Conclusin
Bueno, vamos terminando por hoy con este segundo tutorial sobre el desarrollo de
juegos con Unreal Engine 4. Hoy aprendimos varias: Un mecanismo simple de
deteccin de colisiones, la utilidad del mtodo Tick, le agregamos las acciones de correr
y saltar al personaje, configuramos la cmara de nuestro juego al estilo Side-Scroller
3D, vimos varios macros de variables y funciones de clase para la integracin entre el
cdigo C++ y el Editor, y otras cosillas. Para el prximo tutorial continuaremos nuestro
juego, vamos a agregar un HUD para que el usuario sepa la cantidad de monedas que ha
alcanzado y el tiempo que le queda para lograr su objetivo, vamos a programar el
GameMode para definir las condiciones de GameOver del juego y muchas cosas ms ;)
Espero que te haya sido til y te guste, si es as, djame saber tus comentarios y
comparte este tutorial con el resto de tus amigos tambin apasionados por el desarrollo
de video juegos con Unreal Engine 4. Hasta la prxima !! . . . bye
3
4
FPostConstructInitializeProperties& PCIP);
7
8
/**
* Inicializa las variables SpringArm y SideViewCamera con la
9 configuracion necesaria
1
void InitTopDownCamera(const class FPostConstructInitializeProperties&
1
PCIP);
1
2
/**
1
3 *
S).
1
4 *
1
5
movimiento (positivo o negativo) en esa direccin
1
6 *
2
3
4
5
6
7
: 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 rotacin del Controller.
bUseControllerRotationYaw = false;
8
9
10
//Al estar en true habilita para que el character se rote en la
CharacterMovement->bOrientRotationToMovement = true;
13
14
15
16
17
18
19
20
21
22
23
24
26
27
CoinsCollected = 0;
28
29
30
/** Inicializa SpringArm y SideViewCamera para una vista de juego
31 estilo side-scroller */
FPostConstructInitializeProperties& PCIP)
{
//Inicializando la instancia del USpringArmComponent
SpringArm =
PCIP.CreateDefaultSubobject<USpringArmComponent>(this,
36
TEXT("CameraBoom"));\
37
38
39 capsula de colisin)
40
SpringArm->AttachTo(RootComponent);
41
42
43
44
45
46
47
48
49
SpringArm->TargetArmLength = 500.f;
50
51
52
56 cmara
57
SpringArm->SocketOffset = FVector(0.f,0.f,75.f);
58
59
60
64
65
66
SideViewCamera =
PCIP.CreateDefaultSubobject<UCameraComponent>(this,
67
TEXT("SideViewCamera"));
68
69
71
72
73
74 USpringArmComponent::SocketName);
75
caso
76 }
77
78 /** Inicializa SpringArm y SideViewCamera para una vista de juego
estilo top-down */
79
81 {
CharacterMovement->bConstrainToPlane = true;
82
CharacterMovement->bSnapToPlaneAtStart = true;
83
84
SpringArm =
85 PCIP.CreateDefaultSubobject<USpringArmComponent>(this,
86
TEXT("CameraBoom"));
SpringArm->AttachTo(RootComponent);
87
SpringArm->bAbsoluteRotation = true;
88
SpringArm->TargetArmLength = 800.f;
89
90
SpringArm->bDoCollisionTest = false;
91
92
SideViewCamera =
93 PCIP.CreateDefaultSubobject<UCameraComponent>(this,
TEXT("TopDownCamera"));
94
95
SideViewCamera->AttachTo(SpringArm,
USpringArmComponent::SocketName);
SideViewCamera->bUseControllerViewRotation = false;
96
97
98
99
/**
*
10 S).
0
10 *
2
10 detecta S
3
*/
{
if ((Controller != NULL) && (Value != 0.0f))
{
10
6
10
7
10
8
10
9
11
0
11
1
11
2
/**
11
3 *
D).
11
4 *
11
*/
5
void AHeroCharacter::MoveRight(float Value)
11
6 {
11
7
11
8
11
9
12
0
12
1
1&AHeroCharacter::MoveForward);
Por ltimo, tenemos que encerrar la zona que va a patrullar el enemigo con un objeto de
tipo NevMeshBoundsVolume. Arrastra al nivel, desde la seccin Volumes, un Nav Mesh
Bounds Volume y modifcalo para que encierre todo el nivel. A mi me qued as:
animaciones para el enemigo, que como dijimos, usaremos las mismas animaciones y el
mismo mesh que el personaje principal. Da doble clic en AIEnemyCharacter, seccin
Componentes, selecciona el componente Mesh y en la seccin Mesh para el atributo
Skeletal Mesh selecciona el mismo Mesh que estamos usando para nuestro personaje
protagnico y ajstalo dentro de la capsula como ya hemos hecho antes. En la seccin
Animation selecciona para el atributo Anim Blueprint Generated Class la misma que
estamos usando para nuestro personaje. Selecciona el Capsule Component y cambia el
atributo Capsule Half Height a 96 y Capsule Radius a 42. Esto es para ajustar un poco la
capsula al modelo.
Selecciona el componente CharacterMovement y en la seccin Movement Component
despliega Nav Agent Props en el atributo Agent Radius ponle 42 (este atributo tiene que
ser al menos del tamao del radio de la capsula) y el atributo Agent Height ponlo en 192
(este tiene que ser al menos el doble del atributo Capsule Half Height). La propiedad
NavAgentProps define como el sistema de navegacin del NPC interacta con este
componente.
Ahora pasa al Max Walk Speed y ponle 400.
Por ltimo, pasa al modo Defaults y en la seccin AI, para el atributo AIControllerClass,
selecciona el AIController que acabamos de crear, AIEnemyController. Con esto le
decimos al AICharacter que ser controlado por el AIEnemyController.
A diferencia de como hicimos con nuestro personaje, vamos a configurar a nuestro
enemigo COMPLETAMENTE desde el Editor mediante el Blueprint. Aunque en un
prximo tutorial veremos la variante de hacerlo desde C++ para que tu solo tomes la
decisin de la variante que prefieres.
Ahora agrega el AIEnemyCharacter al nivel en la zona que creamos con bastante
espacio. Guarda y corre el juego. Muvete hasta la zona donde pusimos al enemigo,
como vers, ya lo tenemos ah, pero est parado sin hacer nada, y aunque nos
acerquemos a l, ni se inmuta :(. Vamos a arreglar esto, vamos a darle vida.
En el objeto Behavior Tree mediante el Blueprint vamos a disear de forma visual todo
el rbol de comportamiento del personaje y el Blackboard es la fuente de
conocimiento de nuestro personaje AI, o sea, todas las variables globales que necesite
el personaje para poder definir su comportamiento las definiremos aqu y los nodos que
tenga nuestro rbol y necesiten esta informacin, harn referencia a ella mediante los
mtodos Get Blackboard Key As o Set Black Board Key As. Por ejemplo, una variable
que define una parte del comportamiento del personaje es la referencia al actor que tiene
que perseguir si lo ve. Pues en este caso, esa variable o ese conocimiento que necesita
el personaje la tenemos que definir aqu.
Un detalle a aclarar es que en el Blackboard lo que definimos en realidad son parejas de
clave-valor donde el valor ser del tipo de dato del que definimos para la clave.
Enemigo patrullando una zona del nivel
Vamos con la primera tarea que tiene el enemigo, patrullar una zona del nivel. Primero,
pensemos cual es la lgica detrs de alguien que est vigilando un lugar ? Simple,
estar caminando de un lado hacia el otro, en cada punto crtico se detendr unos
segundos y pasar al siguiente, y as en ciclo infinito (a menos que se canse y se quede
dormido :) . )
En el modelo side-scroller que estamos usando en nuestro juego esto es muy simple
porque como todo el desplazamiento se hace en un solo eje, bsicamente el enemigo se
estara moviendo de un lado al otro y nada ms. Por este motivo es que cambiamos el
estilo de nuestro juego temporalmente, para complicar un poco el movimiento que tiene
que tener el enemigo y hacer ms interesante nuestro estudio ;). Vamos a hacer que este
enemigo est caminando por 4 puntos clave del nivel. Se mover al primero, cuando
llegue se detendr unos segundos, pasar para el segundo, se volver a detener y as
Vamos primero a crear los objetos que agregaremos al nivel y usaremos como puntos
clave en el camino. En estos puntos es donde se detendr el personaje antes de pasar al
siguiente y definirn la trayectoria que patrullar. En el Content Browser crea un nuevo
Blueprint que herede de TargetPoint dale de nombre AIEnemyTargetPoint. TargetPoint
es un simple Actor con una imagen que lo identifica en el Editor cuando lo agregamos al
nivel, esta imagen no se ve en el juego, solo en el editor. Dale doble clic y crale una
variable int de nombre Position y mrcale el iconito del ojito que tiene a la derecha de la
variable para hacerla pblica. Esto nos permite acceder al contenido de esta variable
desde un blueprint externo a ella, el mismo concepto de un atributo publico en
programacin orientada a objetos. De hecho, es exactamente esto, AIEnemyTargetPoint
es una clase que hereda de TargetPoint y que tiene un atributo pblico de nombre
position y tipo int. En este atributo vamos a tener el orden, por llamarlo de alguna
manera, que tiene que seguir el personaje en su recorrido.
Muy bien, ya tenemos listo al enemigo y tambin los puntos que definen el camino que
tendr que patrullar, ahora solo falta implementarle la inteligencia para que se mueva
de un punto a otro en ciclo y esto lo haremos mediante el Behavior Tree y sus
componentes. Vamos a agregarle las primeras ramas al rbol de comportamiento de
nuestro personaje.
Antes, djame comentarte en general cual sera la lgica que seguir ese personaje para
lograr su comportamiento. En el nivel tenemos 4 puntos que marcan el recorrido, el
personaje necesita saber en que punto est actualmente del recorrido y la posicin de ese
punto en el espacio (vector 3D). Una vez que registre al punto al que se va a mover, que
actualice su siguiente punto. Cuando termine el movimiento que espere unos segundos
en la zona y pase al siguiente punto.
Abre el BT que creamos y en el en el panel de Detalles en la seccin BT selecciona para
el atributo Blackboard Asset el Blackboard que acabamos de crear. Ahora abre el
Blackboard y vamos a agregar a este los pares Clave-Valor que necesitamos. Da clic en
el botn de agregar nueva variable y ponle de key TargetPointNumber de tipo de dato
int. Crea otra clave y ponle TargetPointPosition de tipo de dato Vector. En la primera
vamos a guardar el punto al que se tiene que dirigir el personaje y en
TargetPointPosition vamos a tener la posicin de ese punto para poderle decir despus
que se mueve hacia ah.
Ahora vamos a crear el primer Nodo de nuestro BT. Crea un nuevo Blueprint en el
Content Browser que herede de BTTask_BlueprintBase y ponle de nombre
UpdateNextTargetPointTask. BTTask_BlueprintBase es la clase que nos brinda el UE4
para los nodos de tipo Task del BT y que se van a usar mediante el Blueprint.
Los nodos Task sern las hojas del rbol (los nodos que no tienen hijos) y su objetivo es
ejecutar una accin determinada. Este Task que acabamos de crear lo que har ser
comprobar el valor que tiene la clave TargetPointNumber en el Blackboard y validar
que si es mayor igual que 4 la reinicia a 0 para que del cuarto punto pase de nuevo al
primero. Obtendr la posicin en el espacio del TargetPoint al que le toca moverse y
settear el valor de la entrada TargetPointPosition del Blackboard, para que en otro Task
se ejecute la accin de moverse a ese punto. Esto se pudiera hacer en este mismo Task,
pero vimos que uno de los objetivos ms claros que tiene el BT es separar las acciones
en subacciones concretas. Por lo que para esta tarea de patrullar en general la zona
tendremos tres Task que sern nodos hijos de una secuencia.
Dale doble clic al Task que acabas de crear y agrega dos variables TargetPointNumber y
TargetPointPosition ambas de tipo BlackboardKeySelector. Ahora comienza a agregar
nodos y hacer conexiones hasta que el rbol te quede como el siguiente:
no voy a ir paso a paso en como tienes que hacer esto, porque si has seguido los
tutoriales anteriores lo tienes que saber hacer. Por supuesto, s vamos a explicar la lgica
que sigue el algoritmo.
Comenzamos el algoritmo cuando se dispara el Evento Execute del Task, lo primero que
hacemos es comprobar que el valor que est registrado en el TargetPointNumber del
BlackBoard sea mayor que 4. Recuerda, en esa variable tenemos el Position del
TargetPoint al que tiene que moverse el personaje, por lo que tenemos que comprobar
que si llega al ltimo reinicie su valor al primero. Para obtener el valor de una variable
que tenemos en el BlackBoard se usa el nodo GetBlackBoard As [Tipo de dato], en este
caso int y le tenemos que decir cual es el Key que vamos a modificar y es para esto que
creamos las dos variables al inicio de tipo BlackBoardKeySelector.
Una vez que tenemos el Position del TargetPoint al que le toca ir al NPC vamos a
buscarlo en el nivel. Para esto usamos el Nodo Get All Actors of Class que le podemos
definir en el puerto Actor Class el tipo de clase que queremos obtener, aqu
seleccionamos AIEnemyTargetPoint y nos retornar en el puerto de salida un arreglo
con todos los actores que hay en el nivel de tipo AIEnemyTargetPoint.
Despus recorremos este arreglo con el nodo ForEach, tenemos que castear cada
elemento del ciclo a AIEnemyTargetPoint y eso lo hacemos con la ayuda del nodo Cast
To AIEnemyTargetPoint, dentro del ciclo preguntamos en cada iteracin si la propiedad
Position (que creamos como publica en el Blueprint del AIEnemyTargetPoint) es igual
al valor que tenemos registrado en el TargetPointNumber del BlackBoard, y si es igual
obtenemos la posicin en el espacio de ese actor y seteamos el valor del otro Key que
tenemos en el Blackboard, el TargetPointPosition para almacenar ah el vector con la
posicin del siguiente TargetPoint al que se mover el enemigo en su recorrido.
Por ltimo, como dijimos, los Task tiene que terminar de dos formas: con xito o con
Fallo. Segn la forma en la que termine definir el comportamiento de su nodo padre.
En este caso, como todo el proceso lo tendremos dentro de un Sequence, necesitamos
que termine en xito para que el nodo Sequence siga ejecutando al siguiente nodo.
Fjate que al salir del puerto Completed del ForEach, el que se ejecuta cuando termina el
ciclo, incrementamos primero el valor del TargetPositionNumber, para que la prxima
vez que se ejecute este Task se obtenga la posicin del siguiente TargetPoint en el nivel.
Por ltimo terminamos la ejecucin del Task con el nodo FinishExecute, asegrate de
marcar el puerto Success. Guarda y compila.
Ahora solo queda configurar el Behavior Tree con este Task. Abre el
AIEnemyBehaviorTree. Inicialmente solo tenemos un nodo, el nodo raz del rbol.
Arrastra desde el borde inferior del nodo Root y conecta a este un nodo Sequence, este
nodo los usaremos cuando queramos ejecutar un grupo de Task en secuencia, una detrs
de la otra, comenzando por la izquierda, siempre ten en cuenta que para que se ejecute
el siguiente Task, el anterior tiene que haber retornado con xito.
Arrastra del borde inferior del nodo Sequence y agrega el Task
UpdateNextTargetPointTask, selecciona el nodo y en el panel de detalles en la seccin
Default, el campo Target Point Number seleccinale la opcin TargetPointNumber y en
Target Point Position selecciona la opcin Target PointPosition. Estos combos lo que
listan son todos los keys que tenemos registrado en el Blackboard, lo que estamos
haciendo es definiendo que esas dos variables que tenemos en el Task representan esos
Keys del Blackboard
Un momento sin tener que correr te dars cuenta que con esto no es suficiente,
porque lo que hace el Task es solamente darle valor a las variables TargetPointPosition y
TargetPointNumber y ms nada. Arrastra del borde inferior del Sequence y agrega el
Task: Move To. Este Task por defecto nos lo brinda UE4 y lo que hace es decirle al
AICharacter que se mueva a una posicin determinada. Selecciona en el panel de
Detalles en la propiedad Blackboard Key la variable TargetPointPosition. Con esto
estamos definiendo cual ser el Key del Blackboard que tendr la posicin a donde se
mover el personaje, est posicin es obtenida en el Task anterior segn el TargetPoint
al que le toque ir al personaje.
Con esto es suficiente, pero si corremos en este punto, el personaje ir caminando
pasando por los 4 puntos sin detenerse, y evidentemente esto har que se canse ms
rpido y se nos quede dormido al momento :S para evitar esto agrega un tercer Task
al sequence, ahora el Task Wait. Este Task, tambin incluido en el UE4 nos permite
detener la ejecucin del rbol un tiempo determinado. En el panel de Detalles en el
atributo Wait Time puedes definir el tiempo que estar detenido, yo puse 2.
Listo !!, el BT te tiene que quedar as.
Es vlido aclarar en este punto un detalle. El Nodo Wait lo que hace es detener
completamente la ejecucin del rbol, por lo que si en este intervalo de tiempo nos
acercamos al personaje, el NPC no nos detectar, ya que todo el rbol est detenido. De
cualquier forma creo que es un ejemplo vlido del uso del Task Wait.
Configurando el AIController para que use el Behavior Tree que define su
comportamiento
Solo nos falta una cosa, s, ya tenemos configurado el Behavior Tree, pero en ningn
lado le hemos dicho al AIController que use este BT para definir su comportamiento.
Para esto, abre el AIEnemyController agrega el nodo Begin Play y conctalo a un nodo
de tipo Run Behavior Tree en el puerto BTAsset selecciona el AIBehaviorTree. Tan
simple como eso.
Compila, ejecuta el juego y muvete hacia el enemigo. Vers como estar rondando de
punto a punto y en cada uno esperar 2 segundos antes de continuar. :) puedes en lo
que est en ejecucin el juego abrir el Behavior Tree para que veas porque rama est el
rbol en cada momento.
Objects al puerto de entrada Array del ForEachLoopWithBreak para hacerle saber a este
cul es el array que va a iterar.
El puerto Loop Body del ForEachLoopWithBreak es el flujo del programa en cada
iteracin del ciclo, entonces, dentro del ciclo hacemos otro Branch para preguntar si el
Actor que se encontr es el Player y si es as lo guardamos en el Blackboard en el key
TargetActorToFollow. El MultiSphereTrace for Objects lo que retorna es un array de
HitResults esta estructura tiene muchsima informacin del punto de interaccin, no es
solo el actor, por lo que necesitamos el nodo Break Hit Result para obtener el actor, este
nodo en el puerto de salida Hit Actor nos da el Actor. Fjate que para obtener el Pawn
usamos el mtodo Get Player Character que ya hemos usado en los tutoriales anteriores.
Fjate tambin que una vez que setteamos el valor del TargetActorToFollow pasamos el
flujo al Break del ForEach porque ya no necesitamos ms nada.
Bien, este es el caso donde se encuentra al personaje protagnico dentro de la esfera.
Pero para el caso en el que el Actor que se encuentre dentro de la esfera no sea el
personaje protagnico o que el mtodo MultiSphereTrace for Objects retorne false, que
quiere decir que no hay ningn Pawn dentro de la esfera, hay que dejar la variable
TargetActorToFollow en NULL para poder determinar, con otro tipo de nodo del BT
que veremos ahora, si tenemos al personaje protagnico cerca y lo seguimos, o no, y
entonces seguimos con la rutina de vigilancia normal.
Por ltimo, solo para testear, puedes agregar un nodo Print String cuando seteamos el
valor del TargetActorToFollow con el texto Detectado Enemigo cercano. Este nodo
imprime en la pantalla el texto que le indiquemos como parmetro y nos servir aqu
para ver el resultado de nuestro Service en ejecucin
Finalmente, el Blueprint te tendr que quedar as:
Guarda y ejecuta el juego. Veras el debug en rojo de la esfera que se ejecuta alrededor
del enemigo. Recuerda que esto es un debug, despus le quitas el valor que le dimos al
parmetro Draw Debug Line y no se ver nada. Ahora camina con el personaje cerca del
enemigo. Inmediatamente que entres dentro del espacio de la esfera, se imprime en la
pantalla el texto Detectado Enemigo Cercano (recuerda agregar ese nodo Print String
al Blueprint para que puedas ver esta salida)
Que hacemos aqu ?. Iniciamos el algoritmo cuando se lanza el evento Execute del Task,
casteamos el Owner Actor a nuestro AIEnemyController, obtenemos el Pawn del
enemigo y usamos un mtodo SUPER GENIAL que nos da el UE4, el nodo AI Move
To. Con este mtodo podemos indicarle a un NPC que persiga a otro actor deja que
lo veas funcionando !!, parece mentira el poder lograr algo tan complejo con un solo
nodo :)
El nodo AI Move To espera como parmetros el Pawn, un Destino que ser un vector
fijo u otro Actor que ser al que perseguir el Pawn pasado en el primer parmetro. En
este caso usaremos el puerto Target Actor y le pasaremos el actor que est guardado en
el TargetActorToFollow del BlackBoard, que contiene la referencia a nuestro personaje
cuando nos acercamos a la zona de vigilancia del enemigo gracias al Service que
creamos hace unos minutos. Por ltimo, llamamos al Finish Execute cuando el AI Move
To retorna On Success que quiere decir que el enemigo nos alcanz. Fjate que aqu
tambin podemos usar el Print String para ver en pantalla exactamente cuando el
enemigo llega a nosotros.
Creando un Decorator en el Behavior Tree
Espera ! ! ! de seguro que ests loc@ por conectar este Task al rbol y probar, pero
tenemos un pequeo detalle. Este Task solamente lo podemos ejecutar si la variable
Listo !! Guarda, compila y ejecuta el juego y muvete cerca del enemigo, cuando te vea
corre rpido para que te le alejes, vers como inmediatamente que nos alejamos se
incorpora a su tarea rutinaria. Si nos quedamos quietos, y nos alcanza, de momento solo
imprimimos en la pantalla un mensaje. En prximos tutoriales haremos algo ms
interesante.
Conclusin
Espero que este tutorial te sirva para iniciarte en el complejo mundo de la IA en los
juegos desarrollados con Unreal Engine 4. Como has notado es un tema relativamente
complejo, por lo que te recomiendo que le des ms de una pasada al ejercicio, pruebes
distintas cosas, juegua con los valores de retorno de los Tasks para que puedas lograr
buena soltura y entender al 100% como es que funciona esta genial herramienta que nos
da el Unreal Engine 4 para implementar la inteligencia artificial de los NPC en nuestros
juegos.
Como puedes notar, es un arreglo de strings con el nombre de los mdulos que usa
nuestro proyecto, por defecto ya vienen incluidos los mdulos bsicos. Como vamos a
usar clases del modulo AIModule, agrega al final del arreglo otro string con el nombre
del modulo. Te quedar de la siguiente forma:
PublicDependencyModuleNames.AddRange(new string[] { "Core",
Ahora si podemos incluir en nuestro proyecto cualquier clase del Mdulo AI y no nos
dar error. Si quieres, para probar exactamente lo que pasa, al final del tutorial prueba
eliminar este tem del arreglo y trata de compilar.
Recordando lo que hicimos en el tutorial pasado
En el tutorial pasado implementamos varios algoritmos mediante visualscripting y los
usamos en nodos del Behavior Tree del AIController para definir el comportamiento del
enemigo. Bsicamente implementamos tres algoritmos:
CheckNearbyEnemy que lo usamos en un Service del Behavior Tree y nos sirve para
determinar en cada update si el personaje principal est cerca del NPC haciendo uso del
MultiSphereTraceForObjects.
UpdateNextTargetPoint que lo usamos en un Task del Behavior Tree para determinar
el siguiente punto de la ruta que patrulla el enemigo
MoveToEnemy que lo usamos tambin en un Task y se llama cuando el algoritmo
CheckNearbyEnemy detecta que estamos cerca del enemigo y haciendo uso del mtodo
AI Move To hacemos que el enemigo nos persiga.
Para este acercamiento a C++ en Unreal Engine 4 vamos a re implementar estos tres
algoritmos pero totalmente desde C++.
Creando el AIController desde C++
Lo primero es crear nuestra clase C++ que heredar de AIController y que ser el
controlador del NPC. Crea una nueva clase desde el Editor File/Add Code To Project.
Selecciona como clase base AIController y de nombre dale AIEnemyCppController,
cuando el Editor te pregunte si quieres abrirla en el IDE le dices que s. Tendrs la
siguiente clase creada:
1
2
3
4
5
6
7
8
9
//AIEnemyCppController.h
#pragma once
#include "AIController.h"
#include "AIEnemyCppController.generated.h"
/** Clase Controladora del NPC del juego */
UCLASS()
class UE4DEMO_API AAIEnemyCppController : public AAIController
1
0
11
1
2
1
3
1
4 {
1 }; GENERATED_UCLASS_BODY()
5
1 //AIEnemyCppController.cpp
6
1 #include "UE4Demo.h"
7 #include "AIEnemyCppController.h"
1
8 AAIEnemyCppController::AAIEnemyCppController(const class
FPostConstructInitializeProperties& PCIP)
1
: Super(PCIP)
9 {
2
0 }
2
1
2
2
2
3
2
4
Compila el proyecto y ejectalo.
Creando el AIEnemyCppControllerBlueprint.
Aunque pudiramos hacer completamente el AIController desde C++. Vamos a crear un
Blueprint a partir de esta clase para poder extender el comportamiento del AIController
desde VisualScripting, en caso que queramos. Crea un Blueprint que herede de
AIEnemyCppController. Yo lo llam AIEnemyCppControllerBlueprint
Abre el AIEnemyCharacter que creamos en el tutorial pasado selecciona el modo
Defaults, seccin AI, y en el atributo AI Controller Class selecciona
AIEnemyCppControllerBlueprint. Guarda y Compila.
Ya cambiamos el controlador de nuestro enemigo, si corres el juego en este punto vers
que el NPC no har absolutamente nada, como es de esperar. Crea un nuevo Behavior
Tree para este estudio, podemos usar el anterior, pero vamos a crear uno nuevo para no
modificar el anterior y que te quede de referencia. Ya sabes como crear un Behavior
Tree (desde el Content Browser: New/Miscellaneous/Behavior Tree) y ponle de nombre
AIEnemyCppBehaviorTree simplemente para distinguirlo del otro.
//AIEnemyTargetPointCpp.h
#pragma once
#include "Engine/TargetPoint.h"
#include "AIEnemyTargetPointCpp.generated.h"
/** TargetPoint con el que definimos los puntos claves del recorrido
del AIEnemyCharacter */
UCLASS()
class UE4DEMO_API AAIEnemyTargetPointCpp : public ATargetPoint
1
0
11
1
2
1
3
1
4
1
5 {
GENERATED_UCLASS_BODY()
1
6
/** Representa el orden que tiene este TargetPoint en el
1 recorrido del personaje (siendo 0 el punto inicial) */
7
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Tutorial
Category")
1
int32 Position;
8
};
1
9
2 //AIEnemyTargetPointCpp.cpp
0
2 #include "UE4Demo.h"
1 #include "AIEnemyTargetPointCpp.h"
2
2
2 AAIEnemyTargetPointCpp::AAIEnemyTargetPointCpp(const class
PCIP)
3 FPostConstructInitializeProperties&
: Super(PCIP)
2 {
4 }
2
5
2
6
2
7
2
8
2
9
Simplemente agregamos a la clase el atributo Position de tipo entero. Gracias al macro
UPROPERTY con el atributo EditAnywhere podemos ver Position desde el Editor y
editar su valor. Con BlueprintReadWrite lo definimos para que tambin lo podamos
manipular desde el Blueprint y Category representa un nombre de seccin para
mostrarlo en el Editor. Vers que el atributo Position sale en el panel de detalles del
actor en una seccin de nombre Tutorial Category.
Cambiando los TargetPoint que tenemos en el nivel por los
AIEnemyTargetPointCpp.
Compila y ejecuta el cdigo. Actualmente en el nivel tenemos los cuatro puntos que
definen el recorrido del NPC, vamos a cambiar estos por los AIEnemyTargetPointCpp
que acabamos de crear desde C++. Selecciona cada uno y elimnalos del nivel. Ahora,
desde el panel Modes que tienes en la esquina superior izquierda tienes la seccin All
Classes, marca esa seccin y localiza la clase AIEnemyTargetPointCpp.
Desde aqu podemos agregar al nivel instancias de esta clase sin tener que crear un
blueprint como habamos hecho hasta el momento. Arrastra cuatro instancias de esta
clase al nivel, ve seleccionando cada una y modificando en el panel de Detalles dentro
de la seccin Tutorial Category el valor del atributo Position a 0,1,2,3 como mismo
hicimos en el tutorial pasado.
3
3
3
4
3
5
3
6
3
7
3
8
Ve con detenimiento por los comentarios de cada lnea para comprender en detalles lo
que se hace y como se usan las clases y mtodos que brinda el Framework.
En la declaracin del mtodo usamos el macro UFUNCTION con el atributo
BlueprintCallable. Esto es para poder llamar a este mtodo desde un Blueprint en caso
que nos haga falta. El mtodo tiene de tipo de dato void porque no retorna nada,
simplemente hace un procesamiento interno sin devolver ningn valor.
En la implementacin si tenemos algunos puntos importantes en los que detenernos.
Primero, fjate que para obtener la referencia al BlackBoard usamos el atributo
BrainComponent del AIController (la clase padre de nuestra clase). El BrainComponent
tiene un mtodo de nombre GetBlackboardComponent que nos permite obtener una
referencia al BlackBoard que est usando este AIController para su base de
conocimiento. Mediante este objeto de tipo UBlackboardComponent podemos usar el
mismo principio que usamos en el Blueprint para settear u obtener un valor del
BlackBoard.
Con SetValueAsTIPO_DE_DATO podemos settear el valor de un KEY determinado. El
primer parmetro es el KEY y el segundo es el valor. Con el mtodo
GetValueAsTIPO_DE_DATO podemos obtener el valor almacenado en un KEY del
blackboard, como parmetro espera el nombre del Key y retorna el valor almacenado en
ese KEY.
Lo primero que tenemos en cuenta es comparar si el TargetPointNumber es superior al
mximo que tenemos en el nivel, si es as, lo ponemos de nuevo en cero, para garantizar
que el recorrido del personaje sea indefinido y siguiendo siempre el mismo orden.
A continuacin usamos un Iterador muy til que nos brinda el Framework. Desde C++
podemos usar TActorIterator para iterar por todos los Actores del nivel de la clase que
indiquemos. En este caso nuestro objetivo es iterar por todos los
AAIEnemyTargetPointCpp
Dentro del loop lo que hacemos es obtener la referencia del objeto actual en el iterador ,
comparamos el valor del atributo Position con el valor que tenemos en la variable
TargetPointNumber que tiene el valor que tenemos en el KEY del BlackBoard, si son
iguales, seteamos el KEY TargetPointPosition del blackboard con la posicin de ese
4
5
6
7
8
9
1
0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
Es muy fcil crear un Task desde C++. Basta con heredar de UBTTaskNode. Para este
caso solo necesitamos sobrescribir dos mtodos. El mtodo ExecuteTask que se llama
cuando el Behavior Tree ejecuta este nodo. Simplemente obtenemos la referencia al
AIEnemyController desde el parmetro OwnerComp con el mtodo GetOwner,
llamamos al mtodo del AIEnemyController UpdateNextTargetPoint que acabamos de
crear y que se encarga de toda la lgica necesaria para determinar y configurar el
siguiente punto del recorrido. Por ltimo retornamos Succeeded, como mismo hicimos
en el tutorial pasado, para que el nodo padre, el Sequence, contine con la ejecucin del
siguiente nodo.
El mtodo GetStaticDescription nos sirve para retornar un string con una descripcin
para este Task. Esta descripcin se ve en el Behavior Tree y resulta muy til para el caso
en el que sea otra persona la encargada de disear el Behavior Tree desde el Editor.
Compila y ejecuta el proyecto. Desde el Editor abre el Behavior Tree, vers que entre
los Tasks que puedes agregar tendrs este que acabamos de crear completamente desde
C++. Modifica el Behavior Tree para que te quede de la siguiente forma:
Compila, guarda y ejecuta el juego. Como vez, tenemos el personaje movindose por
los cuatro puntos exactamente a como lo logramos anteriormente, la diferencia es que
ahora todo lo hemos hecho desde C++.
Implementado el mtodo CheckNearbyEnemy
Vamos ahora a implementar el mtodo CheckNearbyEnemy en el AIController, que es
el mtodo que hace uso del MultiSphereTraceForObjects para detectar si nos hemos
Mu
2
ltiSphereStart,
//Punto de Inicio de la recta que
4 define la esfera
2
Mu
5 ltiSphereEnd,
//Punto de fin de la recta que define
2 la esfera
70
6
0,
//Radio de la esfera
2
Ob
7 jectTypes,
//Tipos de objetos a tener en cuenta
2 en el proceso
fa
8
lse,
//No
queremos
que
se
use
el
modo
2
complejo
9
Ac
3 torsToIgnore,
//Los actores que se van a ignorar
0 aunque esten dentro de la esfera
ED
3
rawDebugTrace::ForDuration,
//El tipo de Debug
1
Ou
3 tHits,
//Parmetro por referencia donde se
2 guardarn los resultados
tr
3
ue);
//si
se
ignora
el
propio
objeto
3
3
//Inicialmente seteamos en NULL el KEY del BlackBoard
4 TargetActorToFollow para en caso de que en el Trace no se tenga
3 resultados, se quede en NULL
5
UBlackboardComponent* BlackboardComponent = BrainComponent>GetBlackboardComponent();
3
BlackboardComponent->SetValueAsObject("TargetActorToFollow",
6
NULL);
3
7
//Si hay resultados en el Trace
3
if(Result == true)
{
8
//Recorremos todos los objetos dentro del OutHits
3
for(int32 i = 0; i < OutHits.Num(); i++)
9
{
4
//Obtenemos el FHitResult actualmente en el ciclo
0
FHitResult Hit = OutHits[i];
4
//Obtenemos la referencia el Character
1
ACharacter* Character =
4
UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);
2
4
//Si el Actor detectado en el Trace es igual al Character
3
//setamos el KEY TargetActorToFollow del Blackboard con
4 la referencia al Character
if(Hit.GetActor() == Character)
4
{
4
BlackboardComponent5 >SetValueAsObject("TargetActorToFollow", Character);
4
}
}
6
}
4
}
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
6
4
La lgica que sigue est mtodo tampoco es necesaria explicarla porque ya lo hicimos
en el tutorial pasado cuando lo implementamos mediante VisualScripting, pero si vamos
a pasar por el cdigo para ver los detalles del Framework y las clases y mtodo que nos
permiten hacer esto mismo desde C++.
Primero preparamos las variables que vamos a pasar como parmetro al mtodo del
Trace. El nico que creo que vale la pena comentar es la variable ObjectTypes que es un
arreglo de TEnumAsByte Este es el tipo de dato del parmetro ObjectTypes que recibe
el mtodo del Trace. Fjate que solamente le agregamos un elemento, el indicador de
Pawn. UEngineTypes::ConvertToObjectType nos permite convertir el ECC_Pawn
(valor de enum correspondiente al Pawn) a un EObjectTypeQuery. La siguiente variable
que declaramos es el arreglo de Actors que va a ignorar el Trace, recuerda que aqu
solamente incluimos al Pawn del enemigo, que lo tenemos en la variable Pawn y
obtenemos la referencia a este mediante el mtodo GetPawn.
Por ltimo declaramos el arreglo OutHits que lo usaremos como parmetro de salida. El
mtodo SphereTraceMultiForObjects recibe este parmetro por referencia y al terminar
la ejecucin, el arreglo de FHitResults queda poblado con los Hits que detecte el
mtodo. Los parmetros por referencia son la variante que tenemos en C++ para tener
ms de un valor de retorno en una funcin.
Y ahora el punto clave de este mtodo, la llamada al
UKismetSystemLibrary::SphereTraceMultiForObjects. SphereTraceMultiForObjects es
un mtodo esttico dentro de la clase UKismetSystemLibrary y ya sabemos lo que
hace :)
Despus de su ejecucin seteamos en NULL el KEY TargetActorToFollow del
Blackboard para que en caso de que no se encuentre el Pawn del personaje dentro del
Trace, que se quede este KEY en NULL. Recuerda que el Decorator que tenemos en el
Behavior Tree para determinar si se ejecuta o no el siguiente Task se basa en comprobar
si el KEY TargetActorToFollow tiene valor.
Creando el CheckNearbyEnemyService desde C++
Como mismo hicimos para el Task anterior, tenemos que crear el Service del Behavior
Tree donde se va a usar este mtodo que acabamos de crear. Compila y ejecuta el
proyecto, desde el editor crea una nueva clase que herede de UBTService y ponle de
nombre UCheckNearbyEnemyBTService. Abrela en el IDE y modifica el .h para que te
quede de la siguiente forma:
1
2
3 #pragma once
4
5 #include "BehaviorTree/BTService.h"
6 #include "CheckNearbyEnemyBTService.generated.h"
7 /** Service del Behavior Tree del AIEnemyCppController que chequea
8 constantemente si el personaje est cerca de nosotros */
9 UCLASS()
1 class UE4DEMO_API UCheckNearbyEnemyBTService : public UBTService
0 {
GENERATED_UCLASS_BODY()
11
1
/** Se llama en cada update del Service */
2
virtual void TickNode(UBehaviorTreeComponent* OwnerComp, uint8*
1 NodeMemory, float DeltaSeconds) override;
3 };
1
4
Ahora pasa al .cpp y modifcalo para que te quede de la siguiente forma:
1
2
3
4
5
6
#include "UE4Demo.h"
#include "AIEnemyCppController.h"
#include "CheckNearbyEnemyBTService.h"
/** Constructor de la clase */
UCheckNearbyEnemyBTService::UCheckNearbyEnemyBTService(const class
FPostConstructInitializeProperties& PCIP)
7
8
9
1
0
11
1
2
1
3
1
: Super(PCIP)
4
{
1
//Nombre del nodo en el Behavior Tree
5
NodeName = "CheckNearbyEnemy";
1
6
//Intervalo de Update
Interval = 0.5f;
1
7
//Aleatorio de desviacin para el update
1
RandomDeviation = 0.1f;
8 }
1
9 /** Se llama en cada update del Service */
2 void UCheckNearbyEnemyBTService::TickNode(UBehaviorTreeComponent*
0 OwnerComp, uint8* NodeMemory, float DeltaSeconds)
2 {
//Llamamos a la implementacin de la clase base primero
1
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
2
2
//Obtenemos la referencia del AIEnemyController
2
AAIEnemyCppController* AIEnemyController =
3 Cast<AAIEnemyCppController>(OwnerComp->GetOwner());
2
//Llamamos al mtodo CheckNearbyEnemy del AIEnemyController que
4
tiene toda la lgica para determinar si el enemigo est cerca o no
2
//y configurar el KEY correspondiente del Blackboard
5
AIEnemyController->CheckNearbyEnemy();
2 }
6
2
7
2
8
2
9
3
0
3
1
No hay mucho que explicar aqu verdad ? En el constructor inicializamos los valores del
Services que ya conoces del tutorial anterior, y en el mtodo TickNode obtenemos la
Muy bien, ya solo nos queda el ltimo paso para terminar el Behavior Tree que
implementamos en el tutorial pasado pero en esta ocasin completamente desde C++.
Implementado el mtodo MoveToEnemy
Vamos a implementar el ltimo mtodo del AIEnemyController, el encargado de llamar
al MoveToActor para comenzar la persecucin :). Abre AIEnemyCppController.h y
agrega la siguiente declaracin
/**
/**
* Hace que el AIEnemyCharacter persiga al actor setteado en el KEY
TargetActorToFollow del Blackboard
* Es usado en un Task del Behavior Tree para perseguir al
personaje principal
* @return El resultado del mtodo MoveToActor (Failed,
AlreadyAtGoal o RequestSuccessful)
*/
EPathFollowingRequestResult::Type
AAIEnemyCppController::MoveToEnemy()
{
//Obtenemos la referencia al BlackBoard
UBlackboardComponent* BlackboardComponent = BrainComponent-
11
1
2 >GetBlackboardComponent();
1
//Obtenemos la referencia al Actor guardado en el KEY
3 TargetActorToFollow del BlackBoard
1
AActor* HeroCharacterActor = Cast<AActor>(BlackboardComponent4 >GetValueAsObject("TargetActorToFollow"));
1
//Iniciamos el proceso de perseguir al personaje
5
EPathFollowingRequestResult::Type MoveToActorResult =
1
MoveToActor(HeroCharacterActor);
6
1
return MoveToActorResult;
7 }
1
8
Fjate que usamos el mtodo MoveToActor de AIController para comenzar la
persecucin, lo dems no creo que necesite explicacin. Aqu usamos el mtodo
MoveToActor pasndole solo el parmetro del Actor que tiene que seguir, pero date una
vuelta por la declaracin del mtodo para que veas los otros parmetros que se le puede
pasar.
Creando el MoveToEnemyBTTaskNode desde C++
Por ltimo nos queda crear el Task que usar este mtodo. Pero, vamos a detenernos
aqu para comentar un poco de teora que se me pas en el tutorial pasado.
En Unreal Engine 4 los Tasks pueden ser de dos tipos. Los que se ejecutan y tienen un
resultado al instante, como el UpdateNextTargetPoint, y los que demoran un tiempo en
terminar su ejecucin. En este caso se encuentra este Task que vamos a crear, ya que la
accin de este Task va a terminar cuando el enemigo llegue al personaje principal, y
esto puede demorar un tiempo.
Este tipo de Task generalmente en su ejecucin retornan InProgress para que el
Behavior Tree sepa que demorar un poco en terminar, y se usa el mtodo TickTask para
estar comprobando la condicin que se tiene que cumplir para que termine la ejecucin
del Task y en ese momento se termina la ejecucin con una llama al mtodo
FinishLatentTask.
Vamos a verlo en la practica con la creacin de este Task. Agrega una clase de nombre
MoveToEnemyBTTaskNode que herede de UBTTaskNode modifica su .h para que te
quede de la siguiente forma:
1
2
3
4
5
6
7
#pragma once
#include "BehaviorTree/BTTaskNode.h"
#include "MoveToEnemyBTTaskNode.generated.h"
/**
*
*/
8
9
1
0
11
1
2 UCLASS()
class UE4DEMO_API UMoveToEnemyBTTaskNode : public UBTTaskNode
1 {
3
GENERATED_UCLASS_BODY()
1
/* Se llama al iniciar este Task, tiene que retornar Succeeded,
4
Failed
o InProgress */
1
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent*
5 OwnerComp, uint8* NodeMemory) override;
1
6
/* Se llama constantemente. Es usado generalmente en Tasks que
1 en el ExecuteTask retornan InProgress */
virtual void TickTask(class UBehaviorTreeComponent* OwnerComp,
7
uint8*
NodeMemory, float DeltaSeconds) override;
1
8
/** @return Una descripcin para este Task. Este texto se ve en
1 el Nodo
al agregarlo al Behavior Tree */
9
virtual FString GetStaticDescription() const override;
2
0 };
2
1
2
2
2
3
Ahora pasa a la .cpp y modifcala para que te quede de la siguiente forma:
1 #include "UE4Demo.h"
"AIEnemyCppController.h"
2 #include
#include "MoveToEnemyBTTaskNode.h"
3
4 /** Constructor de la clase */
5 UMoveToEnemyBTTaskNode::UMoveToEnemyBTTaskNode(const class
6 FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
7
{
8
//Definimos el nombre que tendr este Nodo en el Behavior Tree
9
NodeName = "MoveToEnemy";
1
0
//Activamos para que se llame el TickTask de este task
bNotifyTick = true;
11
}
1
2
/* Se llama al iniciar este Task, tiene que retornar Succeeded,
1 Failed o InProgress */
3 EBTNodeResult::Type
1 UMoveToEnemyBTTaskNode::ExecuteTask(UBehaviorTreeComponent*
4 OwnerComp, uint8* NodeMemory)
{
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
Muy bien, la .h a estas alturas no creo que tenga nada que explicar. Ahora, en la cpp,
primero en el constructor tenemos que poner en true el atributo bNotifyTick para que el
Engine sepa que tiene que ejecutar el mtodo Tick de esta clase.
En el ExecuteTask simplemente llamamos al mtodo MoveToEnemy, este mtodo nos
retorna tres posibles valores Failed, AlreadyAtGoal o RequestSuccessful. Normalmente
el ExecuteTask tendr que terminar en InProgress a menos que el MoveToActor retorne
AlreadyAtGoal que sera dado si se llama el mtodo estando ya pegados al personaje.
De lo contrario retornamos InProgress para dejarle saber el BT que este Task tomar un
tiempo.
Entonces, en el TickTask es donde tenemos que estar comprobando si llega al objetivo,
si esto pasa se fuerza a terminar el Task con el mtodo FinishLatentTask.
Compila y ejecuta el proyecto. Modifica el Behavior Tree para agregar este ltimo Task
y el Decorator correspondiente como mismo lo hicimos en el tutorial pasado. Te
quedar de la siguiente forma:
Conclusin
A pesar de no implementar ninguna funcionalidad nueva en este tutorial, creo que sirvi
para acercarnos un poco ms al mundo de C++ en el Unreal Engine 4. Puedes bajarte de
aqu las clases que hemos creado en este tutorial para una referencia directa.
Con esto terminamos por hoy, en el prximo tutorial le ensearemos a nuestro
personaje a dar puetazos para que se pueda defender cuando el enemigo lo atrape :)
mientras, me encantara escuchar tus comentarios.
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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 direccin en la que est el personaje y le
aplica un movimiento (positivo o negativo) en esa direccin
*
* @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 automticamente 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;
};
97 #include "UE4Demo.h"
"Coin.h"
98 #include
#include "HeroCharacter.h"
99
10
0 AHeroCharacter::AHeroCharacter(const class
10 FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
1
{
10
//Por defecto esta propiedad viene en true para el Character.
2
//Pero en nuestro modelo de desplazamiento, no queremos que el
10 personaje rote en base a la rotacin del Controller.
3
bUseControllerRotationYaw = false;
10
//Configuracin del componente CharacterMovement
4
10
//Al estar en true habilita para que el character se rote en la
5
direccin del movimiento al comenzar el movimiento.
10
CharacterMovement->bOrientRotationToMovement = true;
6
10
//Factor de rotacin para la propiedad anterior.
7
CharacterMovement->RotationRate = FRotator(0.0f, 540.0f, 0.0f);
10
//Bajamos un poco el valor por defecto de MaxWalkSpeed para que
8
10 el personaje camine un poco ms lento.
CharacterMovement->MaxWalkSpeed = 400.0f;
9
11
//Segun el valor de la constante GAME_STYLE llamamos al metodo
0 correspondiente para configurar los componentes SpringArm y camera
111
switch (GAME_STYLE)
{
11
case CameraGameStyleTopDown:
2
InitSideScrollerCamera(PCIP); //Inicializa SpringArm y
11 SideViewCamera para una vista de juego estilo side-scroller
3
break;
11
case CameraGameStyleSideScroller:
4
InitTopDownCamera(PCIP);//Inicializa SpringArm y
11
SideViewCamera
para una vista de juego estilo top-down
5
break;
11
6
default: break;
11
}
7
CoinsCollected = 0;
11
}
8
11
Inicializa SpringArm y SideViewCamera para una vista de juego
9 /**
estilo side-scroller */
12 void AHeroCharacter::InitSideScrollerCamera(const class
0 FPostConstructInitializeProperties& PCIP)
12 {
//Inicializando la instancia del USpringArmComponent
1
SpringArm =
12 PCIP.CreateDefaultSubobject<USpringArmComponent>(this,
2 TEXT("CameraBoom"));\
12
3
//Agregando el springArm al RootComponent del Character (la
12
4
12
5
12
6
12
7
12
8
12
9
13
0
13
1
13
2
13
3
13
4
13
5
13
6
13
7
13
8
13
9
14
0
14
1
14
2
14
3
14
4
14
5
14
6
14
7
14
8
capsula de colisin)
SpringArm->AttachTo(RootComponent);
//bAbsoluteRotation nos permite definir si este apoyo para la
cmara 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 cmara.
//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
cmara
SpringArm->SocketOffset = FVector(0.f,0.f,75.f);
//La rotacin 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 clsico estilo de cmara 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 mtodo AttachTo nos permite agregar un objeto a otro
objeto en un socket determinado. Recibe dos parmetros.
//el primero, el objeto al que vamos a anclarnos, en este
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);
caso
}
/** 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);
14
9
15
0
15
1
15
2
15
3
15
4
15
5
15
6
15
7
15
8
15
9
16
0
16
1
16
2
16
3
16
4
16
5
16
6
16
7
16
8
16
9
17
0
17
1
17
2
17
3
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 direccin en la que est el personaje y le aplica
un movimiento (positivo o negativo) en esa direccin
*
* @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 direccin del movimiento hacia delante
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
17
4
17
5
17
6
17
7
17
8
17
9
18
0
18
1
18
2
18
3
18
4
18
5
18
6
18
7
18
8
18
9
19
0
19
1
19
2
19
3
19
4
19
5
19
6
19
7
19
8
movimiento
/**
* 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
19
9
20
0
20
1
20
2
20
3
20
4
20
5
20
6
20
7
20
8
20
9
21
0
21
1
21
2
21
3
21
4
21
5
21
6
21
7
21
8
21
9
22
0
22
1
22
2
22
3
24
9
25
0
25
1
25
2
25
3
25
4
25
5
25
6
25
7
25
8
25
9
26
0
26
1
26
2
26
3
26
4
26
5
26
6
26
7
26
8
26
9
27
0
27
1
27
2
27
3
27
4
27
5
27
6
27
7
27
8
27
9
28
0
28
1
28
2
28
3
28
4
28
5
28
6
28
7
28
8
28
9
29
0
29
1
29
2
29
3
29
4
29
5
29
6
29
7
29
8
29
9
30
0
30
1
30
2
30
3
30
4
30
5
30
6
30
7
30
8
30
9
31
0
31
1
31
2
31
3
31
4
31
5
31
6
31
7
31
8
Aqu no hemos hecho nada especial. Recuerdas que ya tenamos un mtodo 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 cmara y otro basta con modificar el valor de esta
constante.
Guarda, compila y ejecuta. Yo adems modifiqu el SpringArm del personaje desde el
editor en la seccin 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 inclinacin a la cmara,
pero es cuestin de gusto, djala como la tenamos hasta ahora o ponla a tu gusto, como
prefieras.
comento para que no pienses que estas haciendo algo mal :) . . . simplemente no
tenemos un animador en disposicin 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
Botn 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 situacin en la que tenemos una animacin en partes para dar
una rpida introduccin al Animation Composite, uno de los assets de animacin que
nos brinda el Unreal Engine 4.
Introduccin al Animation Composite en Unreal Engine 4
Animation Composite es un animation asset que nos permite unir ms de una animacin
una detrs de la otra y tratar esta secuencia de animaciones como una sola animacin.
En este ejemplo lo vamos a usar para unir las animaciones MontageExample_Start,
MontageExample_Punch_1 y MontageExample_End_1 en una sola animacin.
En el Content Browser selecciona: New/Animation/Animation Composite, selecciona el
esqueleto de nuestro hroe y ponle de nombre PunchAnimComposite. Dale doble clic
para editarlo.
Las otras dos secciones (Notifies y Curves) no las veremos en este tutorial, pero sobre
todo los Notifies son sper tiles, nos sirven para definir eventos en puntos exactos de
la animacin y que podemos usar desde el Blueprint para implementar alguna funcin
determinada en ese preciso momento. Por ejemplo, reproducir un efecto de sonido
cuando la animacin 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 animacin de caminar.
Arrastra desde el Asset Browser las animaciones MontageExample_Start,
MontageExample_Punch_1 y MontageExample_End_1 a la seccin Composite. Fjate
que se crean dos filas y las animaciones se agregan alternadamente entre una y la otra
fila para facilitar la visualizacin. Con los controles de reproduccin puedes ver como
queda la composicin de esas tres animaciones en una sola.
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:
Hasta ahora, la programacin de todo lo que tena que ver con las entradas del usuario y
las acciones que se ejecutaban, y en general la lgica del character, las tenamos
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 . . . despus 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 tambin de tipo bool. No es necesario que la variable tenga
el mismo nombre pero creo que es ms claro.
Modifica el Event Graph del AnimationBlueprint del Character para que te quede de la
siguiente forma:
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
modifcala para que te quede de la siguiente forma:
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, asegrate de desmarcar la opcin de Loop, ya que no queremos que esta
animacin se reproduzca en loop.
Compila y ejecuta el juego:
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 animacin 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, vers como se reproduce la animacin del golpe pero en ese
momento se estar desplazando por el escenario patinando. Como nico pudiramos
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 ms genial que nos brinda el UE4 y es el
Animation Montage.
Introduccin 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
animacin hacia el Blueprint, permitindonos en cualquier momento, pausar la
animacin, reproducir una seccin determinada de la animacin, definir al vuelo que
animacin se va a reproducir despus de la actual, saltar a reproducir otra animacin,
loopear de forma sper simple un grupo de animaciones y disparar eventos en cualquier
punto de la animacin, semejante a los Notifies. En fin, nos da un control total de las
animaciones a nivel de cdigo (ya sea C++ o VisualScripting).
Ya tenemos las secciones que conforman nuestro AnimMontage solo nos falta el ltimo
paso: Configurar la relacin entre cada una de las secciones. Fjate que debajo de la
zona Montage tienes la zona Sections con dos botones Create Default y Clear. Primero
da clic en el botn Clear para restaurar cualquier configuracin que tomen por defecto
las secciones. Debajo de estos botones tendrs 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. Despus de dar en el botn Clear tienes 4 fila que representan cada una de las
secciones en donde ninguna seccin tiene relacin con otra.
Vamos a modificar esto, vamos conformar la primera seccin a partir de la animacin
PunchStart despus de reproducirse PunchStart queremos que seguidamente se
reproduzca Punch1 para lanzar el puetazo con la mano derecha. Bien, vamos a hacer
esto hasta aqu.
Primero da clic en el botn verde que dice PunchStart de la primera fila, debajo del
botn Preview All Sections, notars que se pondr en amarillo ahora toca de los botones
alineados horizontalmente arriba, el de nombre Punch1. Vers que automticamente se
agrega el Punch1 a continuacin del PunchStart. Te quedar as:
Muy bien, ahora pasa al EventGraph y modifcalo para que te quede de la siguiente
forma:
Vamos a analizar lo que hemos hecho aqu, aunque estoy bastante seguro que si has
seguido los tutoriales anteriores no tendrs ningn 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 animacin de las que estn en un AnimMontage y Montage Play
que nos permite reproducir un AnimMontage determinado. Al usar el Montage Play se
reproduce la primera seccin 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, fjate que el segundo
puerto (o parmetro) 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 parmetro Montage To Play nos permite definir
que AnimMontage reproducimos, incluso nos permite indicar una velocidad de
reproduccin mediante el parmetro In Play Rate.
Esta validacin del Montage Is Playing la hacemos porque recuerda que este algoritmo
se reproduce en cada update del proceso de animacin del personaje, y en el tiempo en
el que se reproduce cualquiera de las secciones que tenemos definidas en el
Para el trabajo con Montage desde el blueprint tambin contamos con el nodo Montage
Set Next Section. Este nodo nos permite definir cual es la siguiente seccin que se va a
reproducir despus que termine una determinada. Con el parmetro Section Name to
Change definimos cual es de la que vamos a partir, en cada caso segn el evento, ser
Punch1 o Punch2 y con el parmetro Next Section definimos la siguiente seccin que se
va a reproducir, que igual, segn sea el evento sera PunchEnd1 o PunchEnd2.
Tambin contamos con el nodo Jump Tu Section, que nos permite exactamente en ese
momento interrumpir la animacin que se est reproduciendo y reproducir otra seccin.
Listo, guarda, compila y ejecuta el juego. Toca la tecla R indistintamente, prueba tocarla
y dejarla unos segundos y despus soltarla. Notars que cuando se suelta la tecla R
segn la mano en la que se qued dando el puetazo es la que se usa para terminar la
animacin del puetazo genial, verdad !!??
Vale, pero an nos va quedando un problemita. Si intentas caminar en lo que ests
dando golpes vers que patinas en vez de caminar. Como dijimos, esto lo podemos
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 despus 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 locomocin del personaje, y en la
salida del PunchSlot tenemos la animacin del puetazo, pero Layered blend per bone
necesita tambin como parmetro 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 seccin 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 parmetro del
Layered blend per bone. El otro conctalo al PunchSlot y la salida del PunchSlot
conctala al segundo parmetro del Layered blend per bone. Por ltimo el Layered
blend per bone conctalo al Final Animation Pose. Te quedar el grfico de la siguiente
forma:
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 animacin. Selecciona el Layered blend per
bone y en el panel de propiedades de este nodo en la seccin Config tiene la propiedad
Layer Setup que es un arreglo con un solo elemento. Despliega el elemento 0 y este
tambin es un arreglo con un solo elemento de nombre Branch Filters. En este punto,
Branch Filters no tiene ningn elemento. Da clic en el botn 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:
Listo, compila, guarda y ejecuta el juego. Una cosa importante a mencionar y que
notars en cuanto tires el primer puetazo. Vers que el modelo del personaje se
deforma un poco, esto pasa por lo que hablamos al inicio, estas animaciones que
puetazos 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 puetazos al mismo tiempo, vers que funciona a la
perfeccin, a medida que la parte inferior del personaje mueve los pies con la animacin
de caminando o corriendo la parte de arriba reproduce al puetazo. Tal como queramos.
Conclusin
Ya nuestro personaje tiene las habilidades de dar puetazos, 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 dao y cuando su salud se termine morir. Eso nos permitir
dar una introduccin a los mecanismos de colisin que nos brinda el UE4, a los
mecanismo de dao, a como reproducir efectos de sonidos, veremos como usar los
geniales recursos que tenemos a la mano en el marketplace y varias cosas ms. No te lo
pierdas, puedes seguirme en Twitter (@nan2cc) para que ests 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 ingls, pero aunque no se te
de bien el ingls, despus de pasar por este tuto podrs entender muy bien todo. Adems
vers un ejemplo de uso de los Notifies que comentamos aqu. Te recomiendo no te lo
pierdas.
Hasta la prxima !!
comporten fsicamente real. Por ejemplo, que los afecta la gravedad, su masa, fuerzas o
impulsos que se les aplica, etc. Los objetos de este tipo en el nivel deben tener como
Object Type, PhysicsBody.
Vehicle: Este es bastante claro, no ? :) .
Destructible: Actores destructibles. Estos los veremos tambin en prximos tutoriales y
de seguro te va a encantar jugar un poco con ellos. Bsicamente son objetos que los
podemos configurar para que se fraccionen y se rompan cuando reciban un impacto, un
efecto genial y que siempre gusta mucho.
Bien, ya sabemos los distintos Object Type que nos da UE4, ahora vamos a investigar
un poco como est configurado por defecto nuestro personaje protagnico para
responder a las colisiones. Abre el Blueprint del personaje, selecciona el modo
Components y selecciona el componente [ROOT]CapsuleComponent. En el panel de
detalles muvete hasta la seccin Collision y despliega la propiedad Collision Presets.
Desde esta seccin podemos configurar el Object Type de este objeto y cmo
reaccionar a las colisiones con los otros elementos del juego. Fjate en la propiedad
Collision Presets, en este caso tiene seleccionado Pawn. El UE4 por defecto nos trae un
grupo de configuraciones predefinidas que son comunes en los juegos y que podemos
seleccionar, y as no tenemos que configurar siempre manualmente como reaccionar
este objeto con los otros con los que colisione. Si expandes esta seccin vers que todas
las propiedades estn bloqueadas y con una configuracin predefinida, prueba variar la
seleccin de Collision Presets a otro que no sea Pawn, notars que cambia la
configuracin. Por supuesto, siempre podemos seleccionar Custom y settear la
configuracin que queramos especficamente para cada Object Type.
Muy bien, ya con esta teora de nuestro lado, podemos pasar a implementar la
funcionalidad para poder golpear al lanzar un puetazo. La lgica detrs de esto sera:
detectar cuando el puo colisiona con el otro personaje y en ese momento implementar
lo necesario para reaccionar al golpe. Vamos a agregar al nivel otro personaje que nos
servir como monigote para practicar nuestros golpes.
Con los recursos que tenemos ahora mismo, poco podemos hacer, pero aqu viene otro
de los enormes regalos que tenemos al usar Unreal Engine: El Marketplace !!. El equipo
de Epic, por si fuera poco el poner este fenomenal motor en nuestras manos, tambin
nos brinda acceso al MarketPlace, la zona en donde podrs encontrar una enorme
cantidad de recursos para tus proyectos, tus prototipos para aprender, etc. Es un lugar
que al menos todas las semanas deberas darle un recorrido para ver que te trae de
nuevo.
Introduccin al Marketplace
Para usar el Marketplace tienes que tener tu subscripcin vlida y el Unreal Launcher,
que si no lo tienes, lo puedes descargar haciendo clic en el editor en la barra superior en
el botn MarketPlace:
Una vez que abras el Unreal Launcher tendrs la pantalla de login. Pon tu usuario y
password y Bienvenido al paraso !! :)
Tmate unos minutos y revsalo, veras el montn de cosas que encontrars, de seguro lo
querrs bajar todo :) .. . . s, es verdad, no todo es gratis, de hecho, la mayora de las
cosas son de pago, pero vamos !! mira el precio y compralo con todo el trabajo que te
ahorrars, la ventaja es enorme !!. De cualquier forma, no te preocupes, lo que vamos a
necesitar en nuestros tutoriales es gratis :). Busca aqu el Animation Pack y descrgalo
(est marcado en rojo en la imagen anterior). El Animation Pack es un paquete con un
montn de animaciones con el mismo modelo que estamos usando en nuestros
tutoriales.
Despus de descargarlo, en la seccin de Library podrs tener acceso a todo lo que
descargues y desde ah lo podrs agregar al proyecto. Agrega el Animation Pack al
proyecto, vers que se te crear en el Content Browser una carpeta de nombre
AnimStarterPack y dentro de ella, todo el contenido de este paquete. Tmate unos
minutos y abre cada una de las animaciones para que veas todo lo que tenemos ahora
para nuestros tutos :). Dentro de la carpeta AnimStarterPack adems de las animaciones
ya importadas tendrs la carpeta Character. Dentro de esta carpeta est el Blueprint, el
AnimBlueprint el Skeletal Mesh y el resto de los recursos necesarios. Aunque de
momento no usaremos el Blueprint del Character que trae por defecto el
AnimStarterPack te recomiendo que le des un vistazo y lo estudies un poquito.
Creando un personaje para golpear
Voy a pasar por estos pasos bastante rpido, porque si has seguido los tutoriales no
tendrs problema en hacer esto por tu cuenta.
Crea una carpeta en el Content Browser que se llame BlueEnemy. Crea un nuevo
Blueprint que herede de Character y ponle de nombre BlueEnemyBlueprint. Crea un
AnimationBlueprint para el esqueleto HeroTPP_Skeleton que est en
/Game/AnimStarterPack y ponle BlueEnemyAnimBlueprint de nombre. Ahora abre el
BlueEnemyBlueprint selecciona el Modo Default y define el Mesh de este Character
con el Mesh del AnimStarterPack y el Animation Mode en Use Animation Blueprint y
el BlueEnemyAnimBlueprint que acabamos de crear.
Pasa ahora para la seccin Components y mueve el Mesh para que quede dentro de la
cpsula y en la direccin correcta
Ahora pasa al modo Graph para abrir el Blueprint de este personaje y crale una
variables nueva de nombre Health y de tipo INT y en valor por defecto ponle 100. Fjate
un detallito interesante, al crear una variable desde el blueprint puedes definirle un
Tooltip. Este Tootip es un texto descriptivo para la variable que se ve cuando se pone el
cursor sobre ella en el panel My Blueprint.
La variable Health ser quien defina la salud de este personaje, cada vez que le demos
un puetazo perder salud hasta que el valor de esta variable llegue a cero, cuando llega
a cero muere.
Ahora abre el AnimationBlueprint para este personaje y crea una maquina de estado
sper simple, solo con el estado Idle.
Vamos a aprovechar esta situacin para poner otro ejemplo del recin aprendido
AnimMontage. En realidad esto que haremos no es necesariamente con el
AnimMontage, pero creo que va bien practicar un poquito lo que acabamos de aprender
para que se pegue, por eso vamos a hacerlo as :) Crea un nuevo AnimMontage como lo
vimos en el tutorial pasado, dale de nombre HitAnimMontage y agrgale las
animaciones Hit_React_1, Hit_React_2 y Hit_React_3 que tenemos en el
AnimStarterKit. Configralo para que te quede de la siguiente forma:
Por ltimo modifica el AnimGraph para agregarle este Slot entre el State Machine y el
Final Animation Pose.
Ya tenemos todo lo necesario para jugar un poco con este personaje medio monigote y
digo medio monigote porque bsicamente lo que har es estar en reposo, cuando reciba
un puetazo expresar su dolor reproduciendo una animacin y cuando su salud llegue a
cero morir.
Para poder ver bien las colisiones entre ambos personajes podemos usar un pequeo
truco. Abre el BlueEnemyBlueprint selecciona el modo Componentes, selecciona
[ROOT]Capsule Component y en el panel detalles muvete hasta la seccin Rendering
y desmarca la propiedad Hidden In Game. Has esto mismo para el personaje
protagnico. Esto nos ayudar a ver en tiempo de ejecucin esta cpsula y nos ayuda a
revisar en detalles las colisiones.
Pero antes de probar esto, tenemos un detallito. El componente que tiene este personaje
para colisionar y bloquear a los objetos, es una cpsula. En nuestro juego solo nos
desplazamos en un solo eje, pero cuando caminas hacia este otro personaje y comienzan
a colisionar y a bloquearse, si intentas seguir caminando nuestro personaje patinar
alrededor de la cpsula rompiendo el modo de desplazamiento de nuestro scroll-side.
Prubalo para que lo veas mejor. Para evitar esto, lo que hice fue agregar un Box
Component al personaje que encierre a la cpsula y las propiedades de colisin las
configuro igual a la cpsula, con el Preset: Pawn. Con esto, al ser recta la cara de la caja,
se evita el problema del desplazamiento forzado.
Listo !!, agrega este personaje al escenario, recuerda que como nuestro juego es un
scroll side y el personaje principal solamente se mueve hacia la derecha o la izquierda
en un solo eje, para que se puedan encontrar ambos tienen que estar alineados.
Guarda, compila, ejecuta el juego y muvete en direccin al BlueEnemy. Cuando los
dos componentes llegan a colisionar, ya no te puedes mover ms. Si recuerdas cuando
miramos la configuracin de colisin para la cpsula, esta tiene marcado como Collision
Response para todos los elementos: BLOCK. Por eso es que al colisionar estos dos
elementos no se pueden traspasar.
Ambos personajes en el punto donde colisionan los componentes que los encierran.
Como ambos estn configurados como Block, aunque intentes seguir movindote en esa
direccin no podrs avanzar ms.
Bien, eso est perfecto. . . ahora, lanza un puetazo presionando la tecla R. Como
notars, no pasar absolutamente nada, y en este caso la mano traspasa el Mesh del otro
personaje. Tenemos que lograr detectar la colisin del puo del personaje con el Mesh
del enemigo. Para esto vamos a irnos por una solucin muy simple, pero suficiente para
nuestro juego. Dicho sea de paso, esta solucin fue tomada del los video-tutoriales de
Epic Games que te coment en el tutorial pasado y que aprovecho para recomendrtelos
de nuevo :)
Vamos a agregar dos esferas que estarn ancladas a los puos del personaje. Cuando se
detecte la colisin de una de esas esferas con el Mesh del otro personaje, es que estamos
golpendolo.
Agregando dos Sphere Components anclados a las manos del personaje para
detectar la colisin cuando se lance un puetazo.
Abre el Blueprint de nuestro hroe en el modo Components. Fjate que en el panel
Components encima de la jerarqua de componentes que forman parte de nuestro
personaje, hay un ComboBox que dice Add Component. Desde este combobox
podemos agregar nuevos componentes al personaje. Despligalo y selecciona una
Sphere, repite el proceso y agrega otra. Mueve las esferas para que queden ms o menos
sobre cada una de las manos del personaje, no te tiene que quedar perfecto esto es solo
temporal. Cmbiale los nombres a esos componentes a PunchRightComponent y
PunchLeftComponent. Puedes seleccionar las dos dando clic en una y con la tecla Ctrl
presionada da clic en la otra. De esta forma podrs modificar una misma propiedad en
ambos componentes al mismo tiempo. Muvete en el panel Details a la seccin
Rendering y desmrcale Hidden in Game. En la seccin Shape, a la propiedad Sphere
radius dale el valor de 15. Recuerda que el Hidden In Game es temporal, solo para
poder ver el componente en el juego y poder revisar mejor las colisiones.
Blueprint del personaje principal en el modo Components con las dos esferas agregadas
En este punto las esferas estn agregadas como componente del personaje, pero tenemos
que anclarlas a las manos del mismo para que se muevan junto con estas cuando se
lance el puetazo.
Hasta ahora solo hemos usado la hoja Event Graph del blueprint del personaje, pero
como ya habrs notado, tambin contamos con una hoja en blanco de nombre
Construction Script. Todo algoritmo que programemos aqu mediante visualscript se
ejecutar en la construccin del objeto. Vendra jugando como el papel del constructor
de nuestra clase. Modifcalo para que te quede de la siguiente forma:
Es simple lo que hacemos aqu, incluso ya lo habamos hecho anteriormente pero desde
C++. Hacemos un AttachTo de un componente a otro, como mismo hicimos con la
cmara y el SpringArm en el segundo tutorial. El nodo Attach To nos permite anclar un
componente a otro y le podemos especificar tambin el Socket al que lo anclaremos.
Todo lo relacionado con los Sockets lo veremos en prximos tutoriales, de momento
vasta con saber que son puntos en el objeto que podemos usarlos para anclar otro objeto.
Fjate que en el parmetro In Socket Name escribimos directamente hand_l y hand_r
para cada uno de las esferas . . . uuumm, de seguro te imaginas que es esto eh ? ;) . . .
pues s, podemos usar como socket, cualquiera de los huesos del esqueleto que usa este
Mesh. Si abres el esqueleto de este personaje vers que los huesos de la mano se llaman
hand_l y hand_r .
El ltimo parmetro del nodo AttachTo es el Attach Type y nos permite definir mediante
tres valores de enum como se afectar la posicin y rotacin de este elemento respecto
al padre. En nuestro caso queremos que lo siga totalmente, as que selecciona la opcin
Snap to Target.
En este punto me gustara comentarte algo. Este es el tipo de cosas que yo en lo
personal prefiero hacerlas en C++, de hecho, si te fijas en nuestra clase Character toda la
creacin de los componentes y los Attach To los hacemos desde C++. Quise en este
tutorial hacerlo mediante blueprint para ver un ejemplo de esta va. Un muy buen
ejercicio para que sigas logrando soltura con el framework de clases es que intentes
hacer esto mismo pero desde C++ y esta vez no te dejar ninguna pista :)
Pues bien, compila, guarda y pasa al modo Components, vers que ahora salen las
esferas ancladas perfectamente a las manos del personaje. Esto pas porque al compilar
se ejecuta el construction script y esta seccin de componentes se actualiza. Genial
verdad !?
Modo Components del Blueprint del Character con los PunchComponents anclados a la
mano del personaje.
Muy bien, casi terminamos aqu, solo nos queda un detalle. Para detectar la colisin
entre este personaje y el enemigo vamos a usar el Evento Overlap que hablamos al
inicio. O sea, podemos saber cuando dos elementos se superponen y lanzar un evento en
ese preciso momento, es esa la tcnica que vamos a usar para detectar cuando
golpeamos al otro personaje, pero para hacer esto bien, tenemos que hacer una
modificacin en las propiedades de colisin de ambos PunchComponents.
Selecciona los dos, PunchRightComponent y PunchLeftComponent desde el modo
Components del Blueprint del personaje y en Collision Presets selecciona OverlapAll y
desmarca el check: Generate Overlap Event. Pero te preguntars Porqu desmarcar esta
opcin sin en realidad necesitamos que se dispare el evento cuando se detecte el
overlap?. Esto es verdad, pero si desde ahora dejamos esto en true, constantemente estos
componentes generaran el evento y si por ejemplo se pasa por al lado del personaje
aunque sea caminando, se disparara el evento. Como es lgico, no es esto lo que
queremos, solo queremos que se detecte el evento si se est golpeando. Por lo que
dinmicamente vamos a poner esta propiedad en true para cada brazo en el momento
preciso y para esto vamos a usar los BranchPoints que tenemos definido en el
PunchingAnimMontage.
Por ltimo, para que el evento se dispare, necesitamos habilitar el Generate Overlap
Event tambin en el otro personaje. Abre el BlueEnemyBlueprint en el Modo
Components selecciona el Mesh y mrcale la propiedad Generate Overlap Event ya que
queremos detectar cuando uno de los dos PunchComponents del personaje colisiona con
el Mesh de este otro.
Listo !! esto es todo lo que necesitamos para detectar la colisin. Vamos ahora a
implementar la lgica de lo que pasa en ese momento.
Causando dao al disparar el evento Overlap entre uno de los dos
PunchComponents del personaje principal y el Mesh de este otro personaje.
Vamos a intervenir el evento Overlap e implementar lo necesario para causar dao.
Desde el BlueEnemyBlueprint en el modo Components selecciona el Mesh y en el panel
de detalles muvete hasta la seccin Events. Fjate que esta seccin tiene un combobox
que dice Add Event, si lo despliegas se listan todos los eventos de este componente que
podemos usar desde cdigo.
Perfecto !!, ya tenemos el momento en donde colisiona el puo con el Mesh del otro
personaje y en ese punto aplicamos un dao. . . pues bien, una de las ventajas que
tenemos al usar el mtodo Apply Damage es que al aplicar un dao se lanza un evento
que podemos intervenir para implementar toda la lgica cuando un personaje recibe el
dao.
Vamos a intervenir este evento en el blueprint e implementar todo lo necesario para
restar la salud del personaje segn el dao que recibi hasta que su salud llegue a cero y
muera. Pero antes de eso nos falta una cosa. Queremos que cuando el personaje reciba
el golpe se reproduzca un efecto de sonido y cuando muera se reproduzca otro efecto.
Vamos a usar este pretexto para tener nuestro primer acercamiento al trabajo con
sonidos en Unreal Engine 4.
Introduccin al trabajo con efectos de sonido en Unreal Engine 4
La msica y los efectos de sonidos pueden marcar la diferencia y ser los responsables de
que te quedes como bobo delante de un juego. Me pas con el Rayman Legends y
hace poco con el Valiant Hearts: The Great War al escuchar su msica. Por cierto, dos
juegos que por nada del mundo te puedes perder :)
En Unreal Engine 4 todo lo referente al trabajo con audio se maneja dentro de unos
objetos llamados Sounds Cues. Un Sound Cue se puede ver como un blueprint
orientado a audio. O sea, siguiendo la misma filosofa del blueprint, del trabajo con
nodos, la asociacin de un nodo a otro etc, se pueden crear complejos efectos de sonido,
mescla entre efectos y muchas cosas ms relacionadas con el audio para nuestro juego.
Al final este Sound Cue lo podemos tratar como un efecto por si solo.
Vamos a ver un simple ejemplo del trabajo con efectos de audio en UE4. Importa desde
el Content Browser estos tres archivos. Son tres efectos cualesquiera para reproducir
cuando el BlueEnemy reciba los golpes, puedes usar unos tuyos si los tienes a mano.
En estos momento Unreal Engine 4 solo permite importar archivos de sonido WAV de
16bits con especificaciones PCM, ADPCM, DVI ADPCM y cualquier sample rate,
aunque recomiendan 44100 Hz o 22050 Hz.
Desde el Content Browser selecciona el botn Import e importa estos tres ficheros.
Vers que se te muestran como Sound Wave. Puedes reproducir cada uno dando clic
derecho sobre l y seleccionando la opcin Play en el men que se despliega. Desde el
cdigo se pueden reproducir estos Sound Wave directamente sin problema, pero en
muchos casos queremos procesar el efecto antes de reproducirlo directamente y para
esto es que usamos los Sound Cue. Vamos a ver ambos casos: Crearemos un Sound Cue
para hacer que se reproduzca aleatoriamente uno de estos efectos cada vez que el
personaje reciba un golpe y cuando muera reproduciremos directamente el tercero, as
mismo como Sound Wave.
Para crear un Sound Cue vasta con dar clic derecho en el Content Browser y seleccionar
Sounds/Sound Cue. Esto te crea un nuevo tem de tipo Sound Cue en los recursos del
proyecto y te abre directamente el Sound Cue Editor. Por defecto el Sound Cue tiene un
nodo Output que representa el resultado final de todo el pre-procesamiento que se haga,
como el Final Animation Pose para el caso de las animaciones. Desde aqu, solamente
con nodos, podrs crear complejos efectos de sonidos, para que tengas una idea, da clic
derecho en una zona en blanco y lee todas las opciones de nodos que puedes crear.
Tomate un tiempo y date un recorrido general por el editor para que lo conozcas un
poco. Honestamente, de este editor yo solo se lo sper bsico ya que no es mi campo,
pero estoy seguro que un profesional en el tema lo exprime por completo :)
Bueno, a lo nuestro, selecciona primero del Content Browser dos de los efectos,
recuerda que puedes usar la tecla Ctrl para seleccin mltiple. Con los dos Sound Waves
seleccionados abre el Sound Cue, da clic derecho en una zona en blanco y selecciona de
la seccin From Selected, la opcin Random: Multiple WAVs y conecta la salida del
nodo Random a la entrada del Output. Te quedar de la siguiente forma:
Sound Cue para reproducir aleatoriamente uno de los dos efectos de sonido.
Con esto acabamos de crear un Sound Cue que podemos manejar como un efecto de
sonido normal, pero al decirle que se reproduzca, l slo se encargar de seleccionar
aleatoriamente uno de estos dos efectos y reproducirlo. Sper verdad !! ?
Pues ya con esto estamos listo para retomar la implementacin de lo que pasa cuando el
personaje recibe el dao.
Interviniendo el evento Any Damage para implementar lo necesario cuando el
BlueEnemy recibe el dao por un puetazo.
Muvete a una zona en blanco del BlueEnemyBlueprint e implementa el siguiente
algoritmo
Muy bien, vamos con detenimiento por todo este algoritmo porque en l usamos varios
nodos que no habamos usado antes. Primero agregamos el Nodo Event Any Damage.
Este evento se dispara cuando este actor recibe dao. En este caso se disparara cuando
se detecta la colisin con el puo del personaje y se llama al Apply damage.
Fjate que desde este evento podemos obtener el valor de dao aplicado, recuerda que
en nuestro caso es 20, adems podemos obtener el Damage Type, el Instigate By y el
Damage Causer que como vimos, se pueden pasar como parmetros al Apply Damage.
Lo primero que hacemos es restarle la cantidad de dao aplicado a la variable Health
que definimos para este personaje para representar su salud y que inicialmente est en
100. El nodo Clamp nos permite limitar el valor entre dos extremos. En este caso 0 y
100, para evitar que al finalizar la operacin la variable tome valor menor que 0 o
mayor que 100 en caso que fuera posible.
Despus de actualizar el valor de la variable Health, usamos un Print String para
ayudarnos a ver lo que pasa en cada momento. Usamos un nodo muy til para el trabajo
con strings, el nodo Append, que nos permite unir dos strings. En este caso unimos los
strings Blue Enemy Health y el valor de la variable Health, fjate que si intentas
conectar la variable Health al puerto B del Append, como son dos variables de tipo de
dato distinto, el editor automticamente nos genera un nodo por el medio que convierte
el tipo de dato int de la variable Health a string para poderlo usar con Append.
Despus de eso tenemos una condicin usando el nodo Branch. Preguntamos si la
variable Health lleg a cero. Si es false, es que al personaje an le queda salud y en este
caso reproducimos una animacin simple para reflejar el golpe. Fjate que aqu usamos
otra ventaja de los Montage. Recuerdas que en el HitAnimMontage que definimos
tenemos tres secciones con tres animaciones de hit distintas que se llaman HitReact1,
HitReact2 y HitReact3, verdad ? Pues aqu con el nodo Random Integer in Rage
generamos un nmero aleatorio entre 1 y 3 creamos un string mediante el Append
uniendo el string HitReact con el nmero generado, y de esta forma obtenemos
aleatoriamente el nombre de una de las secciones definidas en el AnimMontage. Por
ltimo usamos el nodo Play Anim Montage para reproducir la animacin y le pasamos
por el parmetro Start Section name el string generado. De esta curiosa forma y con la
ventaja que nos brindan los AnimMontage, cada vez que el personaje reciba un golpe
tendr una animacin aleatoria para reaccionar al golpe
Por ltimo reproducimos el Sound Cue que creamos hace unos minutos y que vimos
que sera aleatoriamente uno de los dos efectos de dolor. Para esto usamos el nodo Play
Sound at Location. Este nodo espera dos parmetros. El parmetro Sound que es el
archivo de sonido a reproducir puede ser directamente aun Sound Wave o un Sound
Cue, y el segundo parmetro nos permite definir la posicin en el mundo 3D en la que
se reproducir el sonido. En este caso le pasamos la posicin de este personaje.
La otra rama del Branch es cuando la variable Health llega a cero, que significa que ese
ltimo golpe mat al personaje. Pues bien, para este caso lo primero que hacemos es
reproducir una animacin de muerte. Pero fjate que usamos un nuevo nodo para
reproducir una animacin. El nodo Play Animation nos permite reproducir un
AnimSequence directamente sin tener que hacer uso del AnimationBlueprint o de los
AnimMontage. El parmetro Looping tiene que estar en false, ya que queremos que esta
animacin se reproduzca una sola vez y quede ah en el ltimo frame.
Tambin reproducimos un efecto de sonido, en este caso un Sound Wave directo, solo a
modo de demostracin.
Por ltimo, hay un detalle. Si pruebas en este momento vers que todo va de maravillas
pero cuando tumbas al otro personaje con el ltimo golpe, este caer al suelo con su
animacin, pero se quedarn en el medio del camino los dos componentes de colisin, la
capsula y la caja. Para solucionar esto, seguido a la reproduccin del efecto de sonido
usamos el nodo Destroy Component que nos permite destruir un componente
determinado y le pasamos el Capsule Component y el BoxComponent. Otra solucin
puede ser desactivarle la colisin, en vez de destruir por completo el componente.
Seguidamente, usamos el nodo Delay para demorar el algoritmo 3 segundos y por
ltimo destruimos completamente el actor de la escena. Esto provocar que despus de
tumbar al personaje pasaran tres segundos y su cuerpo desaparecer del nivel.
Listo !!, compila, guarda y ejecuta el juego. Muvete hasta donde est el otro personaje
y comienza a golpearlo. Vers que cada vez que le damos un puetazo lanza una de las
animaciones de impacto y cuando su salud llega a cero termina con la animacin de
muerte. Puedes ver con la ayuda de los Print String como disminuye la salud del otro
personaje de 20 en 20 .
Si, si, si !! . . . se que ahora mismo debes estar fijndote en que las animaciones cuando
el personaje recibe un golpe estn terribles, con la reaccin del personaje parece ms a
que le estn haciendo cosquillas :), pero son las animaciones que tenemos a mano :( . . .
ya en nuestro juego, que el equipo de animacin nos haga algo mejor :)
Conclusin
Vamos terminando aqu este tutorial, espero que te hayas divertido dndole puetazos al
BlueEnemy :). Si eres de los que prefiere el trabajo con C++ (como yo) un buen
ejercicio es que intentes implementar todo lo que hemos hecho en este tutorial pero
desde C++, eso te ayudar a alcanzar ms soltura con el framework de clases del
Engine.
En el prximo tutorial vamos a comenzar a darle armamento a nuestro personaje y ha
poner ms accin en nuestro juego. Puedes estar al tanto siguindome en Twitter
(@nan2cc) . . . mientras, me encantara escuchar tus comentarios y si tienes algn tema
especfico del que quisieras un tutorial tambin djame un comentario, har todo lo
posible por complacerte ;) . . . hasta la prxima, bye !!
Uso del LineTrace para detectar colisin con una lnea imaginaria. Con
el uso de este mtodo simularemos la trayectoria del disparo.
efectos de sonido. En fin, organzalo como mejor sea para ti, pero siempre es bueno
tener el Content Browser bien organizado ;)
Despus que importes los .wav de los efectos de sonidos, crea los Sound Cue
correspondientes.
. . . Ya ?? . . . vale, comenzamos.
Introduccin 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 atrs del cinturn. 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 posicin relativa a un hueso
del esqueleto. Estos sockets, que bsicamente son puntos invisibles, los podemos rotar o
trasladar relativos a la posicin del hueso en donde se ha creado y podemos anclar otros
objetos en esta posicin.
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 programacin para poderle decir a la escopeta en que socket se va a
anclar cuando el personaje la recoja.
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-visualizacin el objeto que finalmente anclaremos aqu. De
esta forma podemos ajustar la posicin y rotacin del HolsterSocket en base al punto de
pivote del otro objeto.
En este punto vale aclarar una cosa. Como notars, 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 transformacin (traslacin, rotacin 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 posicin del punto de pivote de cualquier StaticMesh, abrindolo desde el
Content Browser y marcando en el Toolbar del StaticMesh Editor, el botn Pivot Point.
Muy bien, ahora necesitamos mover y rotar el socket para que la SPAS-12 quede en la
posicin correcta, ya teniendo la pre-visualizacin del modelo, es muy fcil ajustar la
posicin y rotacin correcta para el socket. Selecciona el HolsterSocket y con las
herramientas de rotacin y transformacin ve rotando y moviendo el socket hasta que la
SPAS-12 te quede en la posicin correcta.
Un truco bastante til para lograr la posicin exacta es pre-visualizar la animacin que
tendr que ver con este socket, en este caso es la animacin de desenfundar la escopeta.
As puedes detener la animacin 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-visualizacin 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 posicin correcta. Puedes cargar la animacin de Idle_Rifle_Hip para que el
preview del esqueleto se vea con esta animacin. De esta forma te ser ms fcil
posicionar el socket.
Perfecto !!, ya tenemos listo los dos sockets que necesitamos en nuestro personaje.
Vamos ahora ha implementar la lgica 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 lgica comn 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. Adems, tendremos la clase SPAS12Weapon que heredar de
Weapon y ser nuestra escopeta.
Crea una nueva clase que herede de Actor y nmbrala Weapon y crea otra clase que
herede de Weapon y nmbrala SPAS12Weapon.
Abre el archivo Weapon.h y modifcalo para que te quede de la siguiente forma:
1 #pragma once
2
3 #include "GameFramework/Actor.h"
4 #include "Weapon.generated.h"
5
6
/**
1
0{
1
1
GENERATED_UCLASS_BODY()
1
protected:
2
1
3
/**
1
* USphereComponent es un componente en forma de esfera
4 generalmente usado para detectar colisiones simples
* Este ser el Root del arma y con l detectaremos las
1
colisiones
entre el personaje y el arma
5
1
6
1
7
1
8
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category =
"Weapon")
TSubobjectPtr<USphereComponent> BaseCollisionComponent;
*/
1
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category =
9 "Weapon")
2
0
2
1
2
2
TSubobjectPtr<UStaticMeshComponent> WeaponMesh;
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1 public:
3
2
3
3
3
4
3
6
3
7
3
8
3
9
4
0
4
1
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
4 disparo
2
4
3
*/
4
4
UFUNCTION(BlueprintImplementableEvent, Category="Weapon")
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
6
4
6
5
6
6
6
7
: Super(PCIP)
7{
8
bIsEquipped = false;
9
1
0
BaseCollisionComponent =
1 PCIP.CreateDefaultSubobject<USphereComponent>(this,
1 TEXT("BaseSphereComponent"));
1
2
1 USphereComponent
3
RootComponent = BaseCollisionComponent;
1
4
//Crea la instancia del UStaticMeshComponent
1
5
WeaponMesh =
PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this,
1 TEXT("WeaponMesh"));
6
1
7
1
WeaponMesh8 >BodyInstance.SetCollisionProfileName(FName(TEXT("OverlapAll")));
1
9
//Agregamos el UStaticMeshComponent como hijo del root component
2
0
WeaponMesh->AttachTo(RootComponent);
2}
1
2 void AWeapon::OnCollected()
2
2
3
{
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Weapon
Collected !!");
2
4
2
5}
2
6
bIsEquipped = true;
void AWeapon::Reload()
2
{
7
2
8}
2
9
3
Ammo = MaxAmmo;
0
3
1
3
2
3
3
3
4
3
5
3
6
UCLASS()
class UE4DEMO_API ASPAS12Weapon : public AWeapon
{
GENERATED_UCLASS_BODY()
11
12 };
FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
7{
8
MaxAmmo = 4;
1
0
1
1
Ammo = 4;
1
2
1
3
1
4
void ASPAS12Weapon::Fire()
1
5{
1
6
1
7
1
8
1
9
2
2
2
3
2
4
2
6
2
//Lanza un rayo desde TraceStart hasta TraceEnd, retorna true
7 si se encontr alguna colisin y en OutHit el HitResult de la primera
2
8
colisin
bool bHit = GetWorld()->LineTraceSingle(OutHit, TraceStart,
TraceEnd, ECC_Visibility, TraceParams);
2
9
3
0
3
1
if (bHit)
3 prueba)
2
3
3
3
//Dibuja una linea, a modo de Debug, desde la posicion
4 inicial hasta el punto de impacto
DrawDebugLine(GetWorld(), TraceStart, OutHit.ImpactPoint,
3
5 FColor::Red, false, 5.f);
3
6
3
7
3
9
4
0
4
3
4 pistola
4
4
5
4 ShotSoundEffect, GetActorLocation());
6
}
4
7
4
8
4
9
5
0
Ammo--;
}
else //Si la pistola no tiene municiones ...
{
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Sin
5
municiones :(");
1
5
2
5
3
5
UGameplayStatics::PlaySoundAtLocation(this,
4 EmptySoundEffect, GetActorLocation());
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
6
4
6
5
6
6
6
7
6
8
6
9
7
0
7
1
7
2
7
}
}
3
7
4
7
5
parmetro. 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 botn
para activar o desactivar la pre-visualizacin de los sockets, asegrate 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 podrs agregar y configurar sockets
a este StaticMesh prcticamente de la misma forma que hicimos con el esqueleto del
personaje.
Selecciona el botn Create Socket, dale de nombre al nuevo socket FireSocket y
muvelo para posicionarlo en la punta del can.
Captura del Static Mesh Editor con el modelo de la escopeta y con el FireSocket en la
posicin correcta.
Listo !!, guarda y cierra el editor. Regresamos al cdigo del mtodo Fire para seguir
analizndolo. Ya tenemos el punto de inicio del rayo, ahora nos falta el punto final. El
tercer parmetro que recibe el mtodo es el punto final del rayo y este lo obtenemos
gracias a unos mtodos sper tiles que tenemos para los Mesh, son los mtodo
GetForwardVector, GetRightVector, GetUpVector. Estos mtodos nos retornan un vector
unitario en cada una de las direcciones del Mesh. En este caso necesitamos el mtodo
GetRightVector que nos da un vector unitario en la misma direccin del can. Con este
vector y un poco de matemtica 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 operacin nos dar el punto final del rayo
con el que vamos a simular la trayectoria de la bala.
El cuarto parmetro del LineTraceSingle es el canal que usar este rayo para detectar
las colisiones. Recuerdas que en el tutorial anterior vimos una introduccin a las
colisiones en Unreal Engine 4 y que hablamos de los Collision Responses de cada
objeto y que se dividan 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 parmetro 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 seccin Collision vers 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 colisin.
El ltimo parmetro son opciones adicionales que podemos usar para el rayo, de
momento con sus valores por defecto tenemos suficiente. Puedes revisar la
implementacin de esta estructura para que veas los atributos que tiene.
Al llamar al mtodo LineTraceSingle, en la variable bHit tendremos si se colision con
algo o no, y en la variable OutHit tendremos la informacin 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 adems con la ayuda
de los mtodos DrawDebugLine dibujamos, a modo de debug, una lnea para poder ver
la trayectoria del disparo y validar que todo est funcionando como tiene que ser.
En prximos tutoriales vamos a determinar en este punto, si la colisin con el disparo a
sido con un enemigo, y en ese caso le aplicaremos un dao, aunque los mecanismos
para aplicar dao ya los hemos visto en tutoriales anteriores, y sera sper bueno que te
adelantes e implementes esto por tu cuenta.
En caso que bHit est en false, es que no se detect colisin con ningn objeto, esto
puede pasar porque no hay ningn objeto en el trayecto del disparo, al menos a una
distancia menor que ShotDistance. En ese caso tambin imprimimos en pantalla un
texto temporal y dibujamos una lnea a modo de debug.
Despus de esto, si ShotSoundEffect es vlido, reproducimos el efecto de sonido en la
posicin 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.
Ejemplo grfico del inventario. En este caso el inventario estara lleno, pero si
tuviramos, por ejemplo, solo la escopeta, el segundo elemento del cajn estara vaco.
El elemento ideal que tenemos para implementar este tipo de cosas a nivel de
programacin son los arreglos. Un arreglo, vindolo abstractamente, es un cajn en la
memoria con distintos compartimentos. Podemos obtener el elemento que se encuentra
#define INVENTORY_GUN_INDEX
#define INVENTORY_PISTOL_INDEX
3
4
UENUM()
namespace EInventorySlot
enum Type
Gun,
10
Pistol,
11
None
};
12
13
Los dos #defines son simplemente constantes para usar a la hora de acceder a cada
posicin del arreglo. A continuacin 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 sera si el usuario no ha seleccionado ningn
elemento del inventario, ya sea porque no tiene o porque no ha querido, en este caso se
estara defendiendo a puetazos.
Muvete, hasta el final de la clase y agrega los siguientes atributos:
2 OVERRIDE;
3
Inventory.AddZeroed(2);
3
4//Inicialmente no hay ningn elemento del inventario seleccionado
5InventorySelectedSlot = EInventorySlot::None;
Con la primera lnea lo que hacemos es reservar el espacio en memoria para nuestro
inventario. El mtodo AddZeroed de TArray nos permite agregar la cantidad de
elementos que indiquemos como parmetro al arreglo, pero en NULL, o sea, vacos, no
hay nada ah. Es como tener el cajn con todos sus compartimentos vacos.
Adems de esto, inicializamos la variable InventorySelectedSlot en None, o sea, no
tendremos nada equipado, en este caso la nica forma que tendramos de defendernos
son las manos.
Ahora vamos a implementar la lgica para poder recoger el arma del escenario. De
momento lo haremos muy simple, las armas estarn dispersas por el nivel y cuando le
pasemos por arriba, automticamente esta se agregar al inventario si no tenemos
ninguna otra arma de ese tipo. Para esto usaremos el mtodo
ReceiveActorBeginOverlap del personaje. Este mtodo es el que se dispara cuando se
detecta un Overlap entre este Actor y otro. Recuerda que para que se dispare este
mtodo tiene que estar en true el atributo Generate Overlap Events.
Muy bien, sabiendo esto, agrega la implementacin del mtodo
ReceiveActorBeginOverlap en HeroCharacter.cpp:
1 /** Evento cuando este AActor se superpone con otro AActor */
2 void AHeroCharacter::ReceiveActorBeginOverlap(class AActor*
OtherActor)
3
4
{
Super::ReceiveActorBeginOverlap(OtherActor);
5
6
7
8
9
1
1
//Si el arma con la que estamos colisionando NO ha sido
1
equipada, o sea, que est en el escenario ...
2
1
3
if(OtherActorAsWeapon->bIsEquipped == false)
{
1
5
if(OtherActor->IsA(ASPAS12Weapon::StaticClass()))
{
1
//Si el espacio para las armas de tipo escopeta del
6 inventario est libre...
1
7
if(Inventory[INVENTORY_GUN_INDEX] == nullptr)
1
8
1
9
2
0
{
//Colocamos en el primer index del arreglo
Inventory una referencia a este objeto
Inventory[INVENTORY_GUN_INDEX] =
OtherActorAsWeapon;
2
//Adjunta la pistola al Socket del Mesh del
1 personaje, donde guarda las armas de tipo escopeta.
OtherActorAsWeapon->AttachRootComponentTo(Mesh,
2
2 "HolsterSocket", EAttachLocation::SnapToTarget);
2
3
2
4
2
5
2
6
2
7
2
8
9
3
0
3
1
3
2
GEngine->AddOnScreenDebugMessage(-1, 15.0f,
FColor::Red, "Ya tienes una escopeta equipada !!");
}
}
//@TODO:
//Aqu puedes agregar el chequeo de otro tipo de arma,
3 INVENTORY_PISTOL_INDEX
4
3
5
3
6
3
7
3
8
3
9
4
0
}
}
4
1}
4
2
4
3
4
4
4
5
Ve con detenimiento por los comentarios, lnea a lnea, 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. Adems, 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 seccin Static Mesh selecciona el Mesh de la
SPAS12. En este mismo panel, busca la seccin Sounds y vers las dos propiedades
Shot Sound Effect y Empty Sound Effect. Carga en cada una el Sound Cue
correspondiente que creamos al inicio del tutorial, despus de importar los .wav.
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, cmo 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
animacin 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 pequeito problema, y es que estas animaciones
vienen con su propio esqueleto, as que tenemos que apoyarnos de una opcin que nos
Ahora crea los siguientes Montage Sections: EquipShotgun para el inicio, sustituye el
Default por este. IdleShotgun para el inicio de la animacin Idle_Rifle_Hip.
FireShotgun para el inicio de la animacin Fire_Shotgun_Hip, HolsterShotgun para el
inicio de Equip_Rifle_Standing (recuerda que la usaremos tambin para guardar la
escopeta) y por ltimo ReloadShotgun al inicio de Reload_Shotgun_Hip. Te quedar de
la siguiente forma:
Perfecto, vamos a preparar ahora las secciones para este AnimMontage. En el bloque
Sections da clic en el botn Clear. Ahora, como mismo hicimos en el tutorial de los
puetazos, crea las siguientes secciones:
Muy bien, ya tenemos casi listo nuestro AnimMontage. Digo casi listo, porque an nos
queda incorporarle un detalle para la reproduccin de los efectos de sonido, pero de
momento vamos a probar sin ellos. Guarda estos cambios y cierra.
Antes de pasar para el cdigo tenemos que refactorizar dos cosillas en el AnimGraph del
HeroAnimBlueprint y tambin en el Blueprint del personaje, adems 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 lgica la vamos a cambiar para que se ajuste a nuestro
nuevo mecanismo de defensa que ahora cuenta con un inventario con armas, adems de
los puos.
Por otro lado, abre el AnimGraph del HeroAnimBlueprint y cambia el nombre del Slot a
UpperBody (despus lo cambiaremos en el AnimMontage de los puetazos, no te
preocupes). Adems de esto, selecciona el nodo Layered blend per bone y en el panel
Details pon en true la opcin 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 locomocin, quede rotada la columna del personaje, esto nos permite asegurarnos
que la direccin a la que apuntar el cuerpo ser siempre la correcta. Puedes probar
despus poner esta propiedad en false para que veas cual es el efecto ms claramente.
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:
Como notars 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 activaramos 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 cdigo.
Abre el archivo HeroCharacter.h y agrega al final de la declaracin los siguientes
atributos y mtodos
1 /*
2 * Flag para saber si el player est atacando.
3 * Se hace true cuando se presiona el click derecho del mouse y false
cuando se suelta
*/
1 void ActivateInventorySlot1();
2
1
3 /*
1 * Es llamado cuando se presiona la tecla 2.
4 * Enfunda/desenfunda el arma que est en el segundo compartimento
del inventario (si hay alguna)
1
5 */
1 void ActivateInventorySlot2();
6
1
/* AnimMontage para las animaciones del personaje cuando est usando
7
la escopeta */
1
UPROPERTY(EditDefaultsOnly, Category="Animations")
8
1
9
UAnimMontage* UsingShotgunAnimMontage;
2
3 * Se llama en el BranchPoint de la animacin en el momento de
equipar el arma
2
* Ancla el arma seleccionada a la mano del personaje
4
*/
2
5 UFUNCTION(BlueprintCallable, Category="Animations")
2 void OnEquipActiveWeapon();
6
2
/**
7
* Se llama en el BranchPoint de la animacin en el momento de
2 guardar el arma
8
3 UFUNCTION(BlueprintCallable, Category="Animations")
0
3
1
void OnHolsterWeapon();
3
4 void ReloadWeapon();
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
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 mtodo lo llamaremos cuando el jugador presione la
tecla 1 y activar el primer elemento del inventario.
ActivateInventorySlot2: Este mtodo lo llamaremos cuando el jugador presione la
tecla 2 y activar el segundo elemento del inventario. En este tutorial no le daremos
ninguna implementacin, 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 mtodo ser llamado por el BranchPoint que creamos en
la animacin 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 mtodo anterior. Ser llamado por el
BranchPoint que creamos en la animacin 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 mtodo lo llamaremos cuando el jugador presione/suelte el clic izquierdo
del ratn y se encargar de iniciar el ataque con el arma equipada.
ReloadWeapon: Por ltimo, este mtodo 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 lneas al final del mtodo
SetupPlayerInputComponent:
1InputComponent->BindAction("ActivateInventorySlot1", IE_Released,
this, &AHeroCharacter::ActivateInventorySlot1);
InputComponent->BindAction("ActivateInventorySlot2", IE_Released,
3this, &AHeroCharacter::ActivateInventorySlot2);
4
5InputComponent->BindAction("Attack", IE_Pressed, this,
&AHeroCharacter::Attack);
&AHeroCharacter::Attack);
7
InputComponent->BindAction("ReloadWeapon", IE_Released, this,
&AHeroCharacter::ReloadWeapon);
*/
5 void AHeroCharacter::ActivateInventorySlot1()
6{
7
8
9
1
0
1
1
1
2
3
1
4
1
5
}
else //... si el slot de la escopeta NO est vaco ...
{
//Mostramos un log en la pantalla
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red,
1
"Equipando la escopeta");
6
1
7
1
8
if(UsingShotgunAnimMontage)
1
9
2
0
PlayAnimMontage(UsingShotgunAnimMontage);
}
2
1
2
3
2
4
2
//Lanza animacin para guardarla (Section HolsterShotgun del
6 UsingShotgunAnimMontage)
2
7
2
8
if(UsingShotgunAnimMontage)
{
PlayAnimMontage(UsingShotgunAnimMontage, 1.f,
"HolsterShotgun");
2
9
3
0}
3
1 void AHeroCharacter::ActivateInventorySlot2()
3{
2
//@TODO: Implementar la lgica para activar/desactivar el arma
3 del segundo compartimento del inventario
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
{
//Rectificamos que InventorySelectedSlot sea igual a Gun para
if(InventorySelectedSlot == EInventorySlot::Gun)
1
1
1
2
1
3
if(Weapon)
{
//Desancla el arma del HolsterSocket, para anclarla en la
mano
1
4
Weapon->DetachRootComponentFromParent(true);
1
5
1
Weapon->AttachRootComponentTo(Mesh, "HandSocket",
6 EAttachLocation::SnapToTarget);
}
1
7
1}
8
1
9 /**
2 * Se llama en el BranchPoint de la animacin en el momento de
0 guardar el arma
* Ancla el arma al cinturn del personaje
2
1 */
2 void AHeroCharacter::OnHolsterWeapon()
2
{
2
4
2
5
2
6
if(InventorySelectedSlot == EInventorySlot::Gun)
{
//Obtenemos la referencia al arma en el INVENTORY_GUN_INDEX
del arreglo
AWeapon *Weapon = Inventory[INVENTORY_GUN_INDEX];
2
7
if(Weapon)
2
8
{
//Desancla el arma del HandSocket, para anclarla en la
2 mano
9
Weapon->DetachRootComponentFromParent(true);
3
0
3
1
3
2
3
//Reinicia el valor de InventorySelectedSlot a None, para
3 saber que no tenemos ningn arma equipada
InventorySelectedSlot = EInventorySlot::None;
3
4
3
5
3
6
3
7
3
8
3
9
4
0
}
}
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
Estos dos mtodos 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 animacin para desenfundar el arma, y cuando
la animacin est en el punto donde el personaje tiene la mano ya sobre la escopeta,
gracias al BranchPoint que creamos, se llama el mtodo OnEquipActiveWeapon. Este
mtodo 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 mtodo OnHolsterWeapon es lo contrario.
Muy bien, ya tenemos implementada la lgica para equipar/guardar el arma. Ahora
vamos a implementar la lgica para dispararla. Agrega al final del HeroCharacter.cpp la
implementacin del mtodo Attack
1 /** Inicia el ataque con el arma equipada */
2 void AHeroCharacter::Attack()
3{
4
5
6
7
8
9
1
0
1
1
switch (InventorySelectedSlot)
{
1
2
1
3
1
4
1
5
1
6
1
7 escopeta
1
8
if(UsingShotgunAnimMontage)
{
1
9 "FireShotgun");
PlayAnimMontage(UsingShotgunAnimMontage, 1.f,
2
0
}
}
2
1
2
2
Weapon->Fire();
2
3
2
4
2
5
2
break;
}
case EInventorySlot::Pistol:
{
6
2
7
break;
2
8
}
case EInventorySlot::None:
2
9
{
//@TODO: Iniciar ataque con golpes
3
0
3
1
break;
}
3
2
default: break;
}
3
3
3
else //Esta atacando, detiene la accin de atacar (se solt el
4 clic izquierdo del mouse)
3
5
{
IsAttacking = false;
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
}
}
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
3{
4
if(InventorySelectedSlot == EInventorySlot::Gun)
{
//Obtenemos la referencia
8
9
if(Weapon)
1
0
1
1
1
2
if(UsingShotgunAnimMontage)
PlayAnimMontage(UsingShotgunAnimMontage, 1.f,
"ReloadShotgun");
1
1
4
//LLamamos al mtodo Reload del arma
1
5
Weapon->Reload();
}
1
6
1
7
return;
}
1
8
1
9
if(InventorySelectedSlot == EInventorySlot::Pistol)
{
2
//@TODO: Aqu puedes agregar la lgica para recargar la
0 pistola
2
1
2
2
2
}
}
3
2
4
2
5
2
6
2
7
2
8
2
9
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.
Camina hacia la escopeta y cuando la tengas equipada toca la tecla 1 para activarla.
Despus que la tengas lista, da clic con el mouse para que comiences a disparar. Como
configuramos la escopeta para que tenga un mximo de 4 balas, despus de dar el cuarto
disparo no podrs disparar ms, porque te quedaste sin municiones, pero esto no es
problema :) toca la tecla R para que recargues el arma.
Agrega algn objeto al nivel al que le puedas disparar, asegrate de configurarle su
Collision Traces Response en Block para los Trace de tipo Visibility. Equipa la escopeta
y disprale. Vers que en la pantalla se muestra el log con el nombre del objeto, adems
se dibuja un punto rojo en el punto del impacto, que fue exactamente lo que
implementamos. En prximos tutoriales haremos cosas ms interesantes ;).
Por ltimo, vuelve a tocar la tecla 1 para que guardes la escopeta.
Bueno, hasta ahora va genial todo, eh ?! . . . pero an tenemos un detallito pendiente por
agregar. Si revisas en los efectos de sonido que tenemos, an 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 prximo disparo. Pues bien,
vamos a usar estos efectos en cada momento y para esto usaremos los Anim Notifiy.
Introduccin a los Anim Notifies en Unreal Engine 4
Los Animation Notifications o simplemente AnimNotifies nos brindan una forma de
definir eventos en puntos especficos de una animacin. Son comnmente usados para
agregar efectos de sonido en la animacin, por ejemplo, el efecto del pasos en el
momento exacto donde en la animacin queda el pie en el suelo. O para emitir un
sistema partculas.
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 muvete hasta la seccin Notifies. Usa el
timeline para pararte en el punto de la animacin donde el personaje coloca la bala en la
escopeta. Esta accin la hace cuatro veces, o sea, coloca cuatro balas, por lo que
tendremos que agregar cuatro Notifies. En la seccin Notifies da clic derecho justo en el
punto exacto y selecciona Add Notify fjate que se despliegan varias opciones.
Tenemos, PlayParticleEffect y PlaySound, estos dos son notifies pre-creados. Adems
podemos seleccionar New Notify para crear un Notify especfico.
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 va 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.
Repite el proceso para los 4 momentos de la animacin donde el usuario agrega una
bala. Por ltimo, muvete en el timeline hasta el punto en la animacin del disparo
donde el usuario prepara la escopeta para el prximo disparo y agrega otro
PlaySoundNotify con el efecto shotgr1b.wav.
Listo !!, guarda y dale Play. Dispara la escopeta y podrs escuchar detrs de cada
disparo, justo en el momento exacto, sincronizado con la animacin, el efecto
shotgr1b.wav. Ahora toca la tecla R para recargar y podrs escuchar tambin el efecto
shotgload.wav justo en el momento exacto. . . sper verdad !! :)
Refactorizando el Animation Blueprint para los puetazos.
Antes de terminar, tenemos que refactorizar el Animation Blueprint del personaje para
poder dar puetazos cuando no se tenga equipada ningn arma. Primero, abre el
PunchingAnimMontage y cambia el nombre de slot a UpperBody. Ahora, abre el
Animation Blueprint del personaje y modifcalo para que te quede de la siguiente forma:
Para importar los recursos que estn dentro de la carpeta AIEnemy, primero asegrate
de tener en tu proyecto el AnimStarterPack. Despus, abre la carpeta donde se encuentra
ubicado tu proyecto desde el explorador de ficheros del sistema operativo y copia dentro
de Content, la carpeta AIEnemy.
Para el caso del FBX del arma que usar el enemigo, este imprtalo desde el FBX
Import del Unreal como ya hemos hecho antes. Una vez que importes el arma, brela en
el StaticMesh Editor, y como mismo hicimos para el arma del Player en el tutorial
pasado, crale un Socket de nombre FireSocket en la punta del can.
Captura del StaticMesh Editor con el arma del enemigo cargada despus de la creacin
del FireSocket
Abre el editor y vers en el Content Browser los recursos importados. Puedes tomarte
unos minutos para que le des un vistazo a cada elemento.
Por ltimo, desde tutoriales anteriores estamos usando el AnimStarterPack que puedes
descargar del MarketPlace. Pues, el personaje que viene en este paquete es el que
usaremos como enemigo, pero como tendr un arma en su mano, necesitamos crearle un
socket como mismo hicimos para el Player en el tutorial anterior.
Abre el HeroTPP que viene en el AnimStarterPack y crale un socket en la mano,
puedes usar como preview el arma que importamos para poder posicionar correctamente
el socket, llmalo HandSocket.
Por ltimo, agrega al nivel dentro de esta zona, el AIEnemyCharacter que acabamos de
importar y que tendremos en la carpeta AIEnemy. Recuerda ponerlo siempre alineado al
Player Start.
Compila, lanza el juego y muvete hacia el enemigo. Vers cmo estar rondando de
punto a punto y en cada punto esperar 2 segundos. Cuando detecte que estamos cerca
de l se imprime en la pantalla, a modo de log, el mensaje: Detectando Enemigo
Cercano!!
Te recuerdo que si tienes algn problema con esta parte por la que hemos pasado
bastante rpido, dale un vistazo a este tutorial donde implementamos toda esta lgica de
IA paso a paso.
Preparando el arma que usar el enemigo
Como ves, con lo hecho hasta ahora, tenemos el enemigo patrullando una zona del nivel
pero por las animaciones que tiene puedes darte cuenta que le est faltando su arma.
Vamos a agregar un arma en las manos de este personaje siguiendo el mismo principio
que usamos en el tutorial pasado. En ese tutorial, colocbamos algunas armas en el
escenario para que el Player las recogiera, pero si recuerdas, nunca creamos un blueprint
de estas armas, implementamos toda su lgica desde C++ y despus la agregbamos al
nivel desde la seccin All Classes.
Eso est bien, pero en el mundo Unreal una muy buena prctica es crear un Blueprint a
partir de las clases que tengamos implementadas en C++. Esto nos permite, por
ejemplo, modificar varios parmetros de la clase a nivel visual, una sola vez. Por
ejemplo, imagnate que queramos tener en distintas zonas del nivel varias armas del
mismo tipo. Si creamos un blueprint a partir de la clase Weapon que implementamos en
C++, le definimos el Mesh y las propiedades que queramos una sola vez desde el
blueprint, y despus agregamos las instancias de este blueprint al nivel.
Vamos a crear un blueprint a partir de la clase Weapon que implementamos en C++. En
el tutorial pasado implementamos toda la lgica del arma que usa el personaje, la
SPAS12. Si quisiramos usar la misma lgica de esta arma para el arma del enemigo,
pudiramos crear el blueprint a partir de esta clase que ya hicimos, pero por ejemplo,
supongamos que en el arma de este enemigo la lgica del disparo sea distinta. En este
caso creamos la clase para que herede de Weapon que es la clase base de las armas, y
entonces implementamos en ella las funcionalidades especficas de esta arma.
Dentro de la carpeta Weapons crea un nuevo Blueprint que herede de la clase Weapon
que implementamos en el tutorial pasado y ponle como nombre
M1GarandWeaponBlueprint. brelo, y en la seccin Components, muvete hasta la
propiedad WeaponMesh y en el panel de detalle dentro de la seccin Static Mesh
selecciona el Mesh del rifle que importamos al inicio.
El Chilld Actor nos permite agregar como componente de un Actor, otro Actor
cualquiera. En este caso lo usaremos para agregarle el arma.
En este punto me gustara comentarte otro componente parecido, el StaticMesh. Este
nos permite agregar un StaticMesh cualquiera como componente hijo del Actor. Un
ejemplo de su uso pudiera ser si este personaje tuviera un sombrero y el sombrero fuese
un elemento independiente del modelo original. En este caso podemos agregarlo como
StaticMesh al personaje, y despus anclarlo a un socket determinado, como mismo
haremos con el arma.
Entonces, al ChildActor que agregamos ponle como nombre WeaponComponent,
seleccinalo y muvete en el panel de detalles del componente hasta la propiedad Child
Actor Component y aqu seleccinale el blueprint del arma que preparamos para el
enemigo.
Solamente nos queda anclar el arma al socket en la mano del personaje y esto lo
haremos en el Construction Script. En el modo Graph selecciona la pestaa
Construction Script, en la que inicialmente solo tenemos el nodo Construction Script.
Salva los cambios y compila. Cambia al modo componentes y podrs observar como
ahora el rifle sale en las manos de este personaje.
Al correr el juego notars que ya el enemigo tendr el arma equipada y se mover con
ella en todo momento.
Implementado la lgica en el enemigo para que cuando nos detecte nos comience a
disparar
Ya tenemos a nuestro enemigo patrullando con su arma una zona del nivel, pero a pesar
de que el Player se le acerca, no le dispara, as que vamos a implementar la lgica para
cuando nos detecte nos comience a disparar. Para esto crearemos un nuevo Task en el
BT de este personaje, este Task se ejecutar cuando el enemigo detecte que tiene al
Player cerca y bsicamente lo que har es rotarse hacia l y poner en true la variable que
usaremos desde el Animation Blueprint para comenzar la animacin del disparo.
Abre el AIEnemyAnimBlueprint y crea una nueva variable, dale de nombre IsShooting
y de tipo bool. Guardar y cierra este Blueprint.
Crea un nuevo Task para el BT del enemigo y ponle como nombre RotateAndShoot y en
l implementa el siguiente algoritmo.
Este Task se ejecutar cuando se detecte el personaje, pero fjate que el enemigo puede
detectar al Player estando en su direccin o no, por lo que primero tenemos que
garantizar que este rote en la direccin del personaje antes que nos dispare. Esto lo
logramos con un poco de matemtica bsica, rotando el vector de Location del enemigo
y el vector de location del Player y a partir de ese vector resultante creamos un Rotator
que afecte solo el eje X con el nodo Rotation From XVector y con el Rotator resultante
actualizamos la rotacin del enemigo. Hecho esto solo nos resta pasar a true la variable
IsShooting para que comience la animacin de disparo.
Este Task tambin tiene otro detalle. Si recuerdas cuando vimos los tema de IA a fondo
comentamos que los Task puede tener 3 estados. Primero, que termina su ejecucin
satisfactoriamente, segundo, que termina su ejecucin NO satisfactoriamente y un tercer
estado que es mantener el Task en pendiente. Este es el caso en el que el Task sea una
accin que demorar su ejecucin, como es el caso. Recuerda que el task se ejecuta, el
personaje rota hacia nosotros y comienza a disparar. Para hacer esto, y evitar que
mientras el BT del personaje caiga en un ciclo sobre este Task mientras est disparando,
fjate que no llamamos al Finish Execute del Task.
Bien, entonces, como necesitamos que este Task se ejecute solamente si el personaje
detecta que tiene al Player cerca, crearemos un nuevo Decorator en el BT para controlar
esto. Arrastra del borde inferior del CheckNearbyEnemy y crea un nuevo Decorator de
tipo Blackboard. Selecciona el decorator y en la seccin Flow Control de panel de
Detalles, en el atributo Observer aborts selecciona Both. En este caso lo que queremos
es que inmediatamente que IsActorDetected est en false se pase a ejecutar la otra rama,
para que contine como vigilante.
Selecciona el Decorator y en la seccin Blackboard, en el atributo BlackBoard Key,
selecciona IsActorDetected para definirle que este es el key del blackboard que
queremos comprobar y en Key Query selecciona Is Set para definirle el tipo de
condicin que queremos comprobar sobre el IsActorDetected.
Por ltimo arrastra del selector que tiene este Decorator y conctalo al RotateAndShoot.
Listo !! Guarda, compila, lanza el juego y muvete cerca del enemigo. Cuando este te
detecta, se detiene de su tarea de patrullar, y en ese punto la variable IsShooting toma
valor de true. Lo que nos queda es preparar la lgica de la animaciones para cuando esta
variable est en true, se comience a reproducir la animacin del disparo.
Configurando animacin de disparo del enemigo
Como las acciones de este enemigo son bastante bsicas (solamente camina de un lado a
otro y cuando nos detecta, se detiene ah mismo y comienza a disparar desde el lugar) la
lgica de la animacin de disparar la podemos implementar como un nodo nuevo del
StateMachine de este personaje, a este estado se pasar una vez que la variable variable
IsShooting sea true y cuando sea falso se retornar a la animacin de Idle/Walk.
Abre el StateMachine desde el AIEnemyAnimBlueprint y agrega el nodo Shooting con
las transiciones correspondientes, fjate que dentro de Shooting lo que hacemos es
reproducir la animacin Fire_Shotgun_Ironsights. Te quedar de la siguiente forma.
Salva los cambios, compila, lanza el juego y camina hasta el enemigo. Vers que cuando
te le acercas comienza a ejecutar la animacin de disparar, pero una vez que te alejas,
comienza a caminar como si se estuviera deslizando y con la animacin de disparando.
Porque pasa esto?. Porque, a pesar de haber hecho la mquina de estado del enemigo
correctamente, este solo regresa al estado de Idle/Walk una vez que la variable
IsShooting est en false. En el Task RotateAndShoot ponemos esta variable en true una
vez que el personaje est de frente al Player, pero en ningn punto del BT la ponemos
en false.
Vamos a corregir este detalle y lo haremos en el Service CheckNearbyEnemy, en el
punto en donde ya no se detecta que el Player est cerca vamos hacer que la variable
isShooting vuelva a tomar su valor de false, para que el enemigo regrese de nuevo a su
estado de caminando.
Compila, salva los cambios y corre el juego. Una vez que te acercas al enemigo este se
detiene y comienza a disparar y si nos alejamos, continua caminando con la animacin
correcta.
Muy bien !!, ya tenemos a este personaje listo, pero aunque aparentemente nos dispara,
por las animaciones que reproduce, en realidad en el momento del disparo no pasa
absolutamente nada. As que vamos ahora a implementar la lgica del disparo para
poder determinar si le da al Player y poderle aplicar un dao a este.
Implementado lgica del disparo en el arma del enemigo
En el tutorial anterior, implementamos en C++ la lgica del disparo del arma que usa el
Player. En este tutorial vamos a implementar la lgica del disparo del arma que usar el
enemigo desde el Blueprint que creamos para esa arma. En realidad el modo de disparo
es prcticamente idntico y pudiramos usar el ya implementado, pero lo vamos a hacer
aqu de nuevo y desde blueprint, a modo de demostracin.
En la clase Weapon, la clase base para todas las armas, tenemos el mtodo virtual Fire,
para poder implementarlo en cada arma segn el tipo de disparo. Como tenemos el arma
del enemigo en el blueprint M1GarandWeaponBlueprint, implementaremos aqu el
mtodo Fire de ella, pero antes tenemos que corregir algo que se nos escap en el
tutorial anterior.
En la declaracin del mtodo Fire de la clase Weapon, como atributos al macro
UFUNCTION le pasamos BlueprintImplementableEvent. Este atributo permite que el
mtodo pueda ser implementado en el blueprint, pero tenemos que agregarle adems el
atributo BlueprintCallable, para que tambin se pueda llamar el mtodo desde el
Blueprint.
Abre el archivo Weapon.h y modifica los atributos del macro UFUNCTION del mtodo
Fire, para que te quede de la siguiente forma:
UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category =
"Weapon")
Con esto ahora podemos implementar el mtodo Fire en el Blueprint del arma del
enemigo, y adems llamarlo cuando se vaya a disparar el arma.
Abre el M1GarandWeaponBlueprint en el modo Graph y podrs agregar un nuevo
evento, el Event Fire, y una vez agregado el nodo que representa este evento, podemos
implementar toda la lgica que queramos. Vamos a implementar este evento de la
siguiente forma:
cosilla nueva que tenemos en los blueprints. La posibilidad de crear funciones para
encerrar determinada lgica y despus poderla llamar simplemente como llamamos una
funcin cualquiera.
Abre el AIEnemyCharacterBlueprint y fjate que en el panel MyBlueprint, al lado del
botn para crear una nueva variable, tenemos un botn para crear una funcin. Vamos a
crear una nueva funcin de nombre Attack, en nuestro caso ser muy simple, obtenemos
la referencia del WeaponComponent y llamamos al mtodo Fire, pero por ejemplo, si tu
enemigo puede portar distintas armas la lgica de este proceso de atacar puede ser ms
compleja y por eso sera conveniente que la tengas en una funcin aparte, como mismo
haramos en C++.
Despus de creada la funcin Attack, entra en su modo de edicin e implementa lo
siguiente:
Funcin Attack creada en el blueprint del enemigo. Esta funcin se ejecutar cuando el
enemigo dispare su arma.
Listo !! ya tenemos el mtodo Fire del arma y el mtodo Attack del enemigo, solo nos
va quedando el evento que usaremos para llamar al mtodo Attack.
Si abres la animacin Fire_Shotgun_Ironsights en el Persona Editor y la analizas con
detenimiento, vers que el momento exacto del disparo es un poquito despus que inicia
la animacin. Pues bien, ser en ese preciso momento donde ejecutaremos el mtodo
Attack que acabamos de crear y para esto usaremos los Notifies.
Ahora abre el Blueprint Animation del enemigo, agrega el nodo del Notify que
acabamos de crear y llama el mtodo Attack, para que dispare el arma que tiene
equipada cunado la animacin pase por ese punto.
Compila, salva los cambios y corre el juego. Acrcate al enemigo y vers que ya en el
momento del disparo, se lanza el Trace para simular la trayectoria del proyectil, pero
como notars, el trace traspasa el Player. Esto pasa porque en la configuracin de
colisin del Mesh del Player, el Trace Response no lo tenemos configurado para que
bloquee el trace.
Abre el HeroBlueprint en el modo de componentes, ve al rbol de componentes y
selecciona el Mesh. Desplzate a la seccin Colisiones y busca el combo Collision
Presets y selecciona Custom. Luego marca como Block el parmetro Visibility de la
seccin Trace Responses.
Configuracin del Trace Responses en el Mesh del Player para que el rayo que se lanza
cuando el enemigo dispara su arma colisiones con este
Salva y lanza nuevamente el juego. Desplzate hacia el enemigo para que te dispare,
vers como ahora el rayo si impacta en el personaje.
Por defecto, la salud del Player ser 100. Abre el archivo HeroCharacter.cpp y agrgale
al final del constructor lo siguiente:
*/
3
4
9
1
0
1
1
1
AActor* DamageCauser)
2
1
3
{
//Si el player esta vivo ...
if (Health > 0)
1
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
3}
2
4
2
5
2
6
Compila y lanza el juego. Avanza hacia el enemigo para que te comience a disparar.
Vers que cada vez que te dispara, se imprimen en pantalla las vidas restantes del Player
y cuando llega a cero, se imprime el log que queremos.
Perfecto !!, ahora vamos a darle un poco de vida a estos dos momentos,
reproduciendo las animaciones en el Player cuando recibe los disparos y finalmente
cuando muere.
Implementando la animacin de Hit y Death del Player
Para la animacin de muerte del Player usaremos la animacin Death_1 que viene en el
AnimStarterPack. Primero tenemos que hacerle el retarget para usarla en el esqueleto de
nuestro personaje. Busca en el Content Browser, dentro del AnimStarterPack, la
animacin Death_1, dale clic derecho, Retarget Anim Assets/Duplicate Anim Assets and
Retarget. Selecciona de aqu el esqueleto que usa nuestro HeroCharacter.
Abre el HeroCharacter.h y como mismo tenemos un atributo para cargar desde el editor
el montage con las animaciones cuando el personaje tiene equipada el arma, vamos a
crear otro atributo de tipo AnimationAsset para cargarle desde el editor la animacin de
muerte.
1 /* Animacion del personaje al morir */
2 UPROPERTY(EditDefaultsOnly, Category = "Animations")
3 UAnimationAsset *AnimationDeath;
Captura del juego en ejecucin una vez que el enemigo nos mata, y nuestro personaje
queda muerto, tendido en el suelo.
Muy bien, solo nos va faltando un detallito, cada vez que el Player reciba un disparo,
sera genial que reprodujera alguna animacin no crees ? Pero, en este caso tenemos dos
detalles importante a tener en cuenta.
Primero, esta animacin de hit, tenemos que reproducirla mediante un Montage para
poder mezclarlas con la animaciones de caminando/reposo, para que el personaje pueda
caminar cuando reciba el disparo y estas dos animaciones se fusionen. Segundo,
tenemos dos casos, cuando recibe el disparo teniendo el arma equipada y cuando lo
recibe sin tener el arma equipada, por lo que tenemos que usar dos animaciones
distintas.
Para este tutorial usaremos la misma animacin, porque la verdad es que no tengo
ninguna otra a mano y el AnimStarterPack no tiene ninguna animacin para este caso :
( . . . de todas formas creo que a modo de demostracin es suficiente. En el
AnimStarterPack localiza la animacin Hit_React_1 y realiza todo el proceso de
retarget de animacin para usarla en el esqueleto de nuestro hroe.
Abre el UsingShotgunAnimMontage que preparamos en el tutorial anterior. Arrastra la
animacin que acabamos de hacerle el retarget para el final del montage, justo despus
del Reload. Crea una nueva seccin de nombre Hit justo al comienzo de la animacin de
Hit y en el bloque Sections, agrega una que reproduzca el Hit y despus caiga en el Idle
en loop ya que es justamente lo que queremos. El Player recibir el disparo, reaccionar
a este disparo con un pequeo gesto y continuar en su reposo con su arma en la mano.
Ahora crea otro Montage para el caso en el que se reciba un disparo sin tener ningn
arma equipada. Crea un nuevo montage con el nombre HitAnimMontage, ponle como
nombre en el slot UpperBody, el mismo nombre de slot que hemos estado usando para
los montages, y agrgale la misma animacin Hit_React_1 o si tienes otra mano, para
no repetir esta misma , usa la tuya ;)
3{
4
if (Health > 0)
Health -= Damage;
9
//Reproducimos la animacin de "hit" correspondiente segun
1
tenga o no equipada el arma
0
if (InventorySelectedSlot == EInventorySlot::Gun)
1
1
{
PlayAnimMontage(UsingShotgunAnimMontage, 1.f, "Hit");
1
2
1
3
else
{
1
4
PlayAnimMontage(HitAnimMontage, 1.f);
1
5
1
6
2 player ha muerto");
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
Controller->UnPossess();
3
2
3
3
3}
4
3
5
3
6
3
7
3
8
3
9
Compila, salva los cambios y por ultimo acrcate al enemigo. Haz las pruebas con y sin
el arma equipada y as veras al Player reproduciendo la animacin correcta para cada
estado
Ahora solo nos queda implementar algo muy parecido para el enemigo, un muy buen
ejercicio sera que lo intentaras hacer por tu cuenta. De cualquier forma, aqu te dejo
como sera.
Abre el archivo SPAS12Weapon.cpp (el arma que usa el Player) y agrega el siguiente
cdigo en el mtodo Fire() justo despus que le pedimos el nombre al actor impactado,
bsicamente lo mismo que hicimos en el Fire del arma del enemigo.
2NULL);
Recuerda que para poder reproducir este montage en el enemigo, necesitamos tener el
slot configurado en el Animation Blueprint. Abre AIEnemyAnimBlueprint y agrgale un
nuevo Slot y ponle como nombre HitSlot para poder reproducir el Montage que
acabamos de crear. Por ltimo, conecta el StateMachine con este slot y luego este ltimo
al Final Animation Pose.
Service TakingDamage para actualizar el valor del Key TakingDamage del BlackBoard
del enemigo con el valor de la variable TakingDamage que toma valor de true el tiempo
en el que el enemigo se est recuperando del disparo.
Con este Service listo, solo nos queda modificar el BT agregando al inicio del rbol este
Service y a continuacin el Decorator correspondiente para impedir que contine la
ejecucin del rbol en el tiempo en el que el enemigo est recuperndose de un
disparo.
Listo !! Guarda, compila, lanza el juego y divierte un poco intentado matar al enemigo
antes que l a ti ;)
Conclusin
Bueno, esto es todo por hoy, espero que te haya sido til este tutorial. Un muy buen
ejercicio que te puedo recomendar, es que intentes agregar ms enemigos al nivel, tal
vez en posiciones distintas, con distintas armas que causen ms o menos dao, armas
con lgicas de disparo distintas, en fin . . . todo lo que se te ocurra.
Otra cosa que te quera comentar. Como vez, la lgica de recibir dao y usar un arma,
entre el enemigo y el Player es prcticamente idntica. En proyectos reales, donde
generalmente tendremos varios personajes que compartan lgicas muy parecidas o
idnticas, no es nada recomendado implementar estas cosas de forma independiente por
cada personaje. Lo ideal es crear una clase base, que encapsule la lgica comn entre
los personajes. Como las animaciones si sern distintas entre los personajes, estas las
ponemos como propiedades de la clase, para que se pueda definir especficamente para
cada personaje sin afectar la lgica (como hacemos aqu en el HeroCharacter). En este
tutorial lo hemos hecho as sobre todo para demostrar las variantes C++ y Blueprint y
que las puedas comparar e ir familiarizndote con ellas, pero no olvides aplicar este
consejo en un proyecto real.
Ahora s, esto es todo por hoy :). En prximos tutoriales veremos como implementar el
HUD de nuestro juego, que es el mecanismo mediante el que el jugador tiene siempre
en pantalla la salud del personaje, el arma equipada, la cantidad de municiones etc.
Tambin nos servir para implementar una pequea barra de salud sobre este enemigo.
Adems, veremos dos clases muy importantes para el ncleo del juego, el GameState y
el GameMode. En fin, un montn de cosas interesantes vienen en prximos tutoriales,
as que mantente al tanto, y mientras, bueno ya sabes . . . nos encantara escuchar tus
comentarios.
constante actualizacin. De ser as, djame tus comentarios al final del post y
buscamos juntos la solucin.
Imaginemos un caso real. Supongamos que tenemos en nuestro juego al personaje
protagnico y a un grupo de distintos personajes secundarios, ya sean enemigos, aliados
o lo que sea, y queremos ejecutar algn comportamiento en cada uno de estos otros
personajes cuando el PlayerCharacter muere.
Una va de hacer esto es obtener la referencia del Player Character en el Event Tick de
cada uno de esos personajes, y preguntar por alguna variable que tengamos creada en el
Character que defina su estado en el juego.
Esta solucin funciona, es verdad . . . pero es terriblemente mala !!, porque cada uno de
los actores tienen que estar ejecutando un mtodo constantemente para poder saber el
estado del Player, con el consiguiente gasto de recurso que esto implica, sobre todo si
estamos hablando de un juego relativamente grande, donde varios actores estn
ejecutando esto en paralelo. Esto sera mucho ms desastroso si el proceso no fuera solo
preguntar por el valor de alguna variable, sino realizar algn calculo o algo por el estilo,
aqu el gasto de recursos sera an mayor.
Pues es precisamente esta una de las situaciones en donde los Delegates vienen a
salvarnos la vida. Un Delegate nos dan la posibilidad de bindiar a l uno o ms
mtodos de otras clases. De esta forma podemos en cualquier momento llamar este
Delegate y esto nos permitir ejecutar en cada una de esas instancias que bindiaron
mtodos a l, esos mtodos. O sea, que esto nos permite ejecutar un mtodo de otra
clase en el momento que queramos, sin ni tan siquiera tener acceso directo a ese
mtodo. Genial no !!??
Pues si volvemos a nuestro ejemplo, para solucionar este mismo problema podemos
crear un Delegate en la clase del personaje protagnico. Al iniciar el juego (o en
cualquier otro punto que queramos), le decimos a los otros Actores que registren un
mtodo de ellos que se va a llamar inmediatamente en el momento en el que este
Delegate sea lanzado. Hecho esto, en el momento en el que el Player muere, lanzamos
ese Delegate, y automticamente ser llamado en todas las instancias de las clases que
bindiaron mtodos a l, ese mtodo. Mtodo que tendra una implementacin
particular en cada una de las clases. Por ejemplo, en el enemigo se puede ejecutar
alguna animacin de alegra, y en el aliado, el mtodo puede ejecutar alguna animacin
de tristeza, o puede atacar directamente al enemigo . . . o cualquier otra cosa mucho ms
original y menos absurda :)
Mucho mejor esta solucin, verdad ?
Para entenderlo mejor y ver en la prctica como usarlo, vamos a preparar un pequeito
ejemplo. Veremos primero como trabajar con Delegates desde C++ para entenderlo todo
a bajo nivel, despus veremos la implementacin desde los Blueprints, y queda por tu
parte seleccionar la va que ms te guste cuando los vayas a implementar en tu juego ;)
Para este simple ejemplo he creado un proyecto nuevo en UE4 a partir de la plantilla
Top-Down en C++ y le he agregado 2 nuevas clases de Characters: RedCharacter y
GreenCharacter. Despus de esto he creado dos Blueprints a partir de estas clases,
Captura del nivel preparado despus de eliminar los obstculos y agregar las instancias
del RedCharacterBlueprint y GreenCharacterBlueprint
ram1>,
<Param2> )
<RetVal>
Function( <Pa
DECLARE_MULTICAST_DELEGATE_RetVal_<Num>Params( RetV
ram1>,
alType, DelegateName, Param1Type, Param2Type, )
<Param2>,
)
Para nuestro ejemplo usaremos el caso ms simple que es ejecutar un mtodo sin valor
de retorno ni parmetros, pero al final la lgica es la misma para cada caso.
En el caso de la declaracin de los Delegates Single-cast, la lgica es la misma, solo que
se usa una variacin del macro sin la palabra MULTICAST, por ejemplo para el primer
caso sera DECLARE_DELEGATE
Lo primero que vamos a hacer es declarar este delegate. Abre el .h de la clase del
PlayerCharacter y antes de la declaracin de la clase agrega la declaracin del Delegate
de la siguiente forma:
1 DECLARE_MULTICAST_DELEGATE( FMulticastDelegateSample );
En este caso lo que acabamos de declarar es un multi-cast delegate al que se le pueden
bindiar mtodos que no retornan nada ni reciben parmetros. El prximo paso es
agregar una instancia de este delegate en la clase que lanzara el evento. En este caso es
nuestro personaje protagnico as que agrega como variable de clase del personaje, lo
siguiente:
1 FMulticastDelegateSample MulticastDelegate;
Ahora solo nos queda ejecutar este delegate en el momento que queramos. En este caso
vamos a crear un mtodo Die en el Player, este mtodo se llamar cuando el personaje
muera y en ese momento se lazar el delegate. Para adornar un poco el momento de la
muerte, reproduciremos una animacin dentro de este mtodo Die as que agrega
tambin una instancia de tipo UAnimationAsset para cargar el Asset de animacin desde
el Editor, como hemos hecho en tutoriales anteriores.
Agrega lo siguiente en el .h
1/** Asset de la animacion de muerte */
2UPROPERTY(EditDefaultsOnly, Category = Animations, meta =
(AllowPrivateAccess = "true"))
3UAnimationAsset *DeathAnimation;
4
5UFUNCTION(BlueprintCallable, Category=Default)
6void Die();
Ahora pasa al .cpp y agrega la implementacin del mtodo
1 void AUE4SampleCharacter::Die()
2 {
//Reproduce la animacin de muerte
3
if(DeathAnimation)
4
{
5
GetMesh()->PlayAnimation(DeathAnimation, false);
6
}
7
//Lanza el Delegate para notificar en todos los objetos que
8
9 bindiaron mtodo a este delegate que el PlayerCharacter acaba de
1 morir
MulticastDelegate.Broadcast();
0 }
11
Como puedes ver, es sper simple lanzar el delegate. Suficiente con llamar al mtodo
Broadcast(). Una vez que se llama este mtodo se ejecutar cada uno de los mtodos
que fueron bindiados a l en los objetos correspondientes.
Dos cosas a tener en cuenta con el Broadcast() es que lo podemos llamar incluso si al
delegate no se le ha bindiado ningn mtodo, como es en este preciso momento, y otra
cosa importante es que en caso que tengamos bindiado ms de un mtodo, el orden de
ejecucin de estos no es garantizado, as que tenlo en cuenta y no implementes ninguna
lgica que pueda depender de este orden.
Para el caso de los Single-cast, para llamarlos tenemos los mtodos Execute(),
ExecuteIfBound() y IsBound().
Con esto que hemos hecho hasta ahora ya tenemos creado y ejecutamos el delegate en el
momento que queremos, pero no hemos bindiado a l nada, por lo que al ejecutar el
Broadcast() simplemente no pasar nada. As que vamos a implementar el mtodo que
queremos llamar en el GreenCharacter y bindiarlo al delegate.
Bindiando mtodos al multi-cast delegate desde C++
Modifica la clase GreenCharacter para que te quede de la siguiente forma:
1
2
3
4
5
6
7
8
9
1
0
1
1
1
2
1
3
1
//---------------------------// GreenCharacter.h
//---------------------------#pragma once
#include "GameFramework/Character.h"
#include "GreenCharacter.generated.h"
UCLASS()
class UE4SAMPLE_API AGreenCharacter : public ACharacter
{
GENERATED_BODY()
/** Asset de la animacion de muerte */
UPROPERTY(EditDefaultsOnly, Category = Animations, meta =
(AllowPrivateAccess = "true"))
UAnimationAsset *DeathAnimation;
/** Mtodo que se llama en este clase, mediante el
MulticastDelegate del PlayerCharacter cuando este muere */
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
void OnPlayerCharacterDie();
void Die();
virtual void BeginPlay() override;
};
//---------------------------// GreenCharacter.cpp
//---------------------------#include "UE4Sample.h"
#include "GreenCharacter.h"
#include "UE4SampleCharacter.h"
#include "Engine.h" //Para poder usar el
GetWorldTimerManager().SetTimer
void AGreenCharacter::BeginPlay()
{
Super::BeginPlay();
//Obtiene la referencia del PlayerCharacter
AUE4SampleCharacter* PlayerCharacter =
Cast<AUE4SampleCharacter>(UGameplayStatics::GetPlayerCharacter(GetWor
ld(), 0));
//"Bindea" el mtodo OnPlayerCharacterDie de esta clase al
delegate que se lanza cuando el PlayerCharacter muere.
PlayerCharacter->MulticastDelegate.AddUObject(this,
&AGreenCharacter::OnPlayerCharacterDie);
}
/** Mtodo que se llama en este clase, mediante el MulticastDelegate
del PlayerCharacter cuando este muere */
void AGreenCharacter::OnPlayerCharacterDie()
{
//Delay de 1 segundo antes de llamar al mtodo Die de esta clase
GetWorldTimerManager().SetTimer(this, &AGreenCharacter::Die, 1.0,
false);
}
void AGreenCharacter::Die()
{
//Reproduce una animacin de muerte
if(DeathAnimation)
{
GetMesh()->PlayAnimation(DeathAnimation, false);
}
}
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
El mtodo OnPlayerCharacterDie, que es el mtodo que vamos a bindiar al delegate del
PlayerCharacter, simplemente ejecuta un delay y al segundo llama al mtodo Die, que al
igual que el mtodo Die del PlayerCharacter, lo que hace es reproducir una animacin
de muerte. Cuando el PlayerCharacter muera y se notifique a este objeto, el
GreenCharacter esperar un segundo (para asimilar la noticia de que su compaero
muri :( . . . ) y tambin morir . . . Un poco trgica la historia ahora que lo pienso, as
que en tu juego usa los delegates para cosas ms alegres :)
OnPlayerCharaterDie ser el mtodo que se llamar automticamente cuando el
Delegate que creamos en el Character se dispare, pero para esto tenemos que bindiar el
mtodo al delegate y esto vamos a hacerlo en el BeginPlay.
Cuando necesitamos bindiar el mtodo de alguna clase a un delegate determinado, una
buena idea es hacerlo en el BeginPlay de esos actores, para ya no tener que
preocuparnos de eso en el transcurso del juego. Sobrescribe el BeginPlay de esta clase
para que te quede de la siguiente forma:
1
2
3
4
5
6
7
8
9
1
0
void AGreenCharacter::BeginPlay()
{
Super::BeginPlay();
//Obtiene la referencia del PlayerCharacter
AUE4SampleCharacter* PlayerCharacter =
Cast<AUE4SampleCharacter>(UGameplayStatics::GetPlayerCharacter(GetWor
ld(), 0));
//"Bindea" el mtodo OnPlayerCharacterDie de esta clase al
delegate que se lanza cuando el PlayerCharacter muere.
PlayerCharacter->MulticastDelegate.AddUObject(this,
&AGreenCharacter::OnPlayerCharacterDie);
}
tenemos AddUObject y para los single cast tenemos BindUObject. Para eliminar
tenemos el mtodo UnBind()
Listo !! . . . esto es todo lo que necesitamos. Guarda, compila y abre el editor.
Para facilitar la muerte de nuestro personaje (el evento que queremos notificar en todos
los otros personajes) vamos a forzarla a que suceda cuando se toque la barra
espaciadora, no es muy real pero nos permitir centrarnos en el asunto que de verdad
nos interesa en este tutorial. Abre el Project Settings y en la seccin Input agrega una
entrada de tipo Action, ponle de nombre KillPlayerCharacter y que se active con la
barra espaciadora
Ahora abre el Blueprint MyCharacter. Primero en el modo Default carga para la
propiedad DeathAnimation una animacin de muerte cualquiera que puedes obtener
haciendo un Retarget de cualquiera de las animaciones que vienen en el
AnimStarterPack.
Pasa al modo Graph y agrega lo siguiente para que cuando se presione la barra
espaciadora se llame al mtodo Die que implementamos en el PlayerCharacter.
Captura del Event Graph del PlayerCharacter. Ejecuta el mtodo Die cuando se detecta
el InputAction KillPlayerCharacter (Barra Espaciadora)
Hecho eso, compila, guarda y dale Play al juego. Presiona la barra espaciadora para
forzar la muerte de nuestro Character y vers que un segundo despus de la muerte del
personaje protagnico, el GreenCharacter tambin cae al suelo. Lo que nos demuestra
que se llam el mtodo correspondiente en esa instancia en el momento preciso. Genial
verdad !!??
Captura de dos momentos del juego. El primer cuadro, al iniciar el juego. El segundo
cuadro, al tocar la barra espaciadora el PlayerCharacter muere, lanza el Delegate de que
muri, es notificado el GreenCharacter mediante el mtodo OnPlayerCharacterDie,
inicia un Timer por 1 segundo y despus llama al mtodo Die, que hace que el
GreenCharacter muera tambin.
//---------------------------// RedCharacter.h
//---------------------------#pragma once
#include "GameFramework/Character.h"
#include "RedCharacter.generated.h"
UCLASS()
class UE4SAMPLE_API ARedCharacter : public ACharacter
{
GENERATED_BODY()
/** Material a usar en el Mesh de este Character cuando el
PlayerCharacter muere */
UPROPERTY(EditDefaultsOnly, Category = Materials, meta =
(AllowPrivateAccess = "true"))
UMaterial *PlayerCharacterDeadMaterial;
/** Mtodo que se llama mediante el MulticastDelegate del
PlayerCharacter cuando este muere */
void OnPlayerCharacterDie();
virtual void BeginPlay() override;
};
//---------------------------// RedCharacter.cpp
//---------------------------#include "UE4Sample.h"
#include "RedCharacter.h"
#include "UE4SampleCharacter.h"
void ARedCharacter::BeginPlay()
{
Super::BeginPlay();
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
Este mtodo lo que har ser cambiar el Material del Mesh del RedCharacter al que
carguemos desde el Editor en la propiedad PlayerCharacterDeadMaterial. Para el
ejemplo yo prepar un Material de color negro, de esta forma cuando el RedCharacter
sea notificado de la muerte del PlayerCharacter este se pondr negro al instante.
Guarda, compila y lanza el juego. Toca la barra espaciadora y vers como el
RedCharacter cambia a color negro al instante y el GreenCharacter, al pasar un segundo,
cae al suelto.
Captura del juego despus que muere el PlayerCharacter. Son notificados ambos
personajes. GreenCharacter muere al segundo y RedCharacter cambia su color a negro.
Muy bien, con este ejemplo hemos podido ver el enorme potencial de los delegates y la
forma de usarlos desde C++, pero como una de las potencialidades ms grande que tiene
el Unreal Engine es su mecanismo de VisualScripting mediante los blueprints, vamos a
ver como implementar este mismo tipo de mecanismos desde los blueprints.
Introduccin a los Event Dispatchers en Unreal Engine 4.
Como sabes, gracias a la magia que ha logrado el equipo de Epic con los Blueprints
para abstraernos de toda la complejidad que puede tener para muchos el asunto de C++,
todo esto lo podemos hacer sper fcil y rpido tambin desde Blueprints, aqu los
nombres varan un poco pero el concepto es el mismo.
En los blueprints podemos crear Event Dispatchers. Los Event Dispatchers sern
bsicamente los delegates, estos Event Dispatchers solamente los podemos ejecutar o
bindiar a ellos eventos de otros blueprints.
Para ver un ejemplo prctico, vamos a plantearnos algo simptico. Vamos a suponer que
nuestro personaje puede aumentar su fuerza a un nivel extremo y en ese caso se vuelve
negro, en el punto en el que nuestro personaje llega a este nivel, el RedCharacter que
sera su enemigo, se hecha a correr :).
Agrega otra Action al Project Settings/Input y llmalo Activate Super Power (o como
prefieras).
En el Event Graph del Character fjate que en el panel My Blueprint, desde el que
podemos agregar variables y funciones tambin tenemos la opcin para crear un Event
Dispatcher. Da clic aqu para crear uno y llmalo SuperPowerActivated.
Una vez creado, desde el Panel Detalles, en la seccin Inputs, podemos agregar
parmetros de entrada segn el mtodo que queramos ejecutar al llamar a este Event
Dispatcher, en nuestro caso no necesitamos ningn parmetro.
Ahora, modifica el Event Graph de la siguiente forma:
Captura del EventGraph del MyCharacter despus de agregar el llamado del Event
Dispatcher una vez que el personaje incrementa su poder (cuando se detecta la entrada
de nombre ActivateSuperPower)
Listo, este es el sinnimo del Broadcast de C++. Con esto todos los mtodos que se
hayan bindiado a este Event Dispatchers se llamarn. Pero, como mismo vimos en C++,
si no bindiamos ningn evento a este Event Dispatcher no pasar nada, as que vamos a
ello.
Abre el RedCharacterBlueprint crea un Custom Event de nombre
OnPlayerCharacterIncreasePower e implementa en l el siguiente algoritmo.
A partir del Actor Location obtenemos un vector 1000 unidades detrs del personaje y el
aplicamos un AI Move To que har que este personaje salga corriendo hacia ese punto.
Listo !! . . . guarda los cambios y dale Play al juego. Toca la tecla para activar el super
poder, veras como nuestro personaje se vuelve negro, y el RedCharacter sale corriendo
como todo un cobarde :)
Conclusin
Este tutorial ha sido un poco ms corto que los de costumbre pero espero que haya
servido para darte un acercamiento a como trabajar con Delegates en Unreal Engine 4 y
que puedas explotar a partir de ahora esta fenomenal va que tenemos de notificar a los
distintos Actors de nuestro juego cuando un evento determinado ocurre.
Para profundizar en los Delegates desde C++ puedes darte una vuelta por la
documentacin oficial
. . . Y esto es todo por hoy. Seguiremos viendo nuevas cosas de Unreal Engine 4 en
prximos tutoriales as que no te vayas muy lejos. Tambin puedes seguirme en Twitter
(@nan2cc) y as te dejo saber cuando tengamos un nuevo tutorial. Mientras, me
encantara escuchar tus comentarios o temticas que quisieras que tocara en prximos
tutoriales. Hasta la prxima, bye ;)