Upload
phungminh
View
214
Download
0
Embed Size (px)
Citation preview
Introducción 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á, 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.
Modifica un poco el nivel, copiando el objeto Floor y usando las herramientas de Transformación para
crear una plataforma que será la que estará patrullando el enemigo. A pesar que en el estilo final de
nuestro juego no es necesario que la plataforma tenga mucho espacio hacia atrás, para este ejemplo
crésela también en ese eje, con el objetivo de crear un buen espacio para que el enemigo se desplace
libremente y poder probar y jugar mejor con la AI.
Por último, tenemos que encerrar la zona que va a patrullar el enemigo con un objeto de tipo
NevMeshBoundsVolume. Arrastra al nivel, desde la sección Volumes, un Nav Mesh Bounds Volume y
modifícalo para que encierre todo el nivel.
Creado el enemigo a partir de un AIController
En el primer tutorial explicamos la filosofía que seguía el framework de UE4 en su jerarquía de clases en
lo referente a los personajes de nuestro juego, hablamos que el personaje principal era un Pawn especial,
un Character, y este era controlado por el PlayerController. Los personajes que usan inteligencia artificia,
generalmente conocimos como non-player characters (NPC), como los enemigos por ejemplo, funcionan
muy parecidos, su representación en el juego es mediante un Pawn y también son controlados por un
Controller, pero en este caso por un AIController.
Por lo demás, básicamente contienen lo mismo que nuestro personaje principal. Un skeletal mesh, el
sistema de animaciones, el Movement Component etc. Para nuestro ejemplo, haremos al enemigo a partir
de las mismas animaciones y modelo de nuestro personaje protagónico, aunque si quieres variar te
recomiendo de nuevo que te des una vuelta por el Marketplace, ahí podrás encontrar varios modelos de
personajes con sus animaciones listos para usar. Crea una carpeta en el Content Browser, ponle el
nombre que quieras, en mi caso le puse AIEnemy.
Crea dentro de esa carpeta un nuevo Blueprint que herede de AIController y ponle de nombre
AIEnemyController. Crea otro Blueprint que herede de Character y ponle de nombre AIEnemyCharacter.
Ahora vamos a configurar el Skeletal Mesh y las animaciones para el enemigo, que como dijimos,
usaremos las mismas animaciones y el mismo mesh que el personaje principal. Da doble clic en
AIEnemyCharacter, sección Componentes, selecciona el componente Mesh y en la sección Mesh para el
atributo Skeletal Mesh selecciona el mismo Mesh que estamos usando para nuestro personaje
protagónico y ajústalo dentro de la capsula como ya hemos hecho antes. En la sección 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 sección Movement Component despliega Nav
Agent Props en el atributo Agent Radius ponle 42 (este atributo tiene que ser al menos del tamaño 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 navegación del
NPC interactúa con este componente.
Ahora pasa al Max Walk Speed y ponle 400.
Por último, pasa al modo Defaults y en la sección 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 próximo tutorial veremos la
variante de hacerlo desde C++ para que tu solo tomes la decisión de la variante que prefieres.
Ahora agrega el AIEnemyCharacter al nivel en la zona que creamos con bastante espacio. Guarda y corre
el juego. Muévete hasta la zona donde pusimos al enemigo, como verás, 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.
Un poco de teoría sobre la inteligencia artificial en Unreal Engine 4 y el Behavior Tree
En UE4 hay muchas formas de implementar la lógica de un personaje AI, una muy simple es en el mismo
Blueprint del Character, haciendo uso del evento Tick, implementar algún algoritmo para que el personaje
se mueva de un lado a otro, que compruebe que está cerca de alguien con los métodos que brinda el
Engine (y que veremos algunos hoy) y nos ataque o haga cualquier otra cosa. Esto se puede implementar
directamente ahí, pero a medida que necesitemos complejizar las acciones que tendrá ese Actor, los
estados en los que podrá estar y las cosas que podrá hacer en cada situación, se hará mucho más
complejo el control y el mantenimiento de toda esa lógica, ya sea en VisualScript o desde C++.
Este es uno de los motivos por los que el UE4 nos facilita la creación de un Behavior Tree (árbol de
comportamiento) para poder definir el comportamiento que tendrá el NPC según las condiciones en las
que se encuentre. Behavior Tree es un concepto de mucho tiempo de la IA que no vamos a detenernos
en él aquí, si quieres profundizar en la teoría detrás de los arboles de comportamientos para mecanismos
IA puedes poner en Google “behavior tree ai“ y date una vuelta por el resultado que más te llame la
atención ;)
Creando el Behavior Tree y el BlackBoard que definirá el comportamiento del enemigo
Después de está teoría vamos a la práctica. Entra en la carpeta AIEnemy en el Content Browser y
selecciona New/Miscellaneous/Behavior Tree ponle de nombre AIEnemyBehaviorTree y
New/Miscellaneous/Blackboard y ponle de nombre AIEnemyBlackboard
En el objeto Behavior Tree mediante el Blueprint vamos a diseñar 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 información, harán referencia a ella
mediante los métodos 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 lógica detrás de alguien que está vigilando un lugar ?… Simple, estará caminando de un lado
hacia el otro, en cada punto crítico 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, básicamente el enemigo se estaría moviendo de un lado al otro y
nada más. 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 más 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 definirán 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 créale una
variable int de nombre Position y márcale el iconito del ojito que tiene a la derecha de la variable para
hacerla pública. Esto nos permite acceder al contenido de esta variable desde un blueprint externo a ella,
el mismo concepto de un atributo publico en programación orientada a objetos. De hecho, es exactamente
esto, AIEnemyTargetPoint es una clase que hereda de TargetPoint y que tiene un atributo público 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.
Agrega al nivel cuatro AIEnemyTargetPoints y distribúyelos en el escenario por distintos puntos,
definiendo el camino que quieres que recorra el personaje.
Ahora ve seleccionando uno a uno y en el panel de propiedades verás en la sección Default la variable
Position, ve por cada uno y ponle 0, 1, 2, y 3. La idea es que el personaje comience en el punto 0,
después se mueva el 1, después al 2, después al 3 y después de nuevo al 0. A mi me quedó así:
Muy bien, ya tenemos listo al enemigo y también 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, déjame comentarte en general cual sería la lógica 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 posición 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 sección 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 botón 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 posición de ese punto para poderle decir después 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 serán las hojas del árbol (los nodos que no tienen hijos) y su objetivo es ejecutar una
acción 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 posición 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 acción de moverse a ese punto. Esto se pudiera hacer en este mismo Task, pero vimos que
uno de los objetivos más 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 serán 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 lógica 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.
Después 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 iteración 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 posición 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 posición
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. Según 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. Fíjate 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 próxima
vez que se ejecute este Task se obtenga la posición del siguiente TargetPoint en el nivel. Por último
terminamos la ejecución del Task con el nodo FinishExecute, asegúrate 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 raíz 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 detrás 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 sección Default, el campo Target Point Number selecciónale la opción
TargetPointNumber y en Target Point Position selecciona la opción 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 darás cuenta que con esto no es suficiente, porque lo que hace el
Task es solamente darle valor a las variables TargetPointPosition y TargetPointNumber y más 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 posición 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 posición a donde se moverá el personaje, está
posición es obtenida en el Task anterior según 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 más rápido y se nos quede dormido al
momento :S … para evitar esto agrega un tercer Task al sequence, ahora el Task ”Wait”. Este Task,
también incluido en el UE4 nos permite detener la ejecución 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 válido aclarar en este punto un detalle. El Nodo Wait lo que hace es detener completamente la
ejecución 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 válido 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 ningún 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 conéctalo a un nodo de tipo Run Behavior Tree en el puerto BTAsset
selecciona el AIBehaviorTree. Tan simple como eso.
Compila, ejecuta el juego y muévete hacia el enemigo. Verás como estará rondando de punto a punto y
en cada uno esperará 2 segundos antes de continuar. :) … puedes en lo que está en ejecución el juego
abrir el Behavior Tree para que veas porque rama está el árbol en cada momento.
Creando un Service en el Behavior Tree para que el personaje esté al tanto de quien se acerque a
la zona.
Ya tenemos al enemigo haciendo su recorrido rutinario, pero bastante poco eficiente es como vigilante
no ? . . . por más que nos acerquemos a la zona, ni se inmuta :(. Vamos entonces a “enseñarle“ a vigilar
la zona, o sea, a que detecte cuando nos acerquemos.
Como ya vimos, en el BT, los Task son para ejecutar una acción determinada cuando sea el momento
adecuado y retornar un resultado. Pero en este caso no es exactamente esto lo que necesitamos, aquí
necesitamos ejecutar un proceso constantemente ya que un buen vigilante tiene que estar atento,
contantemente, para saber si alguien se aproxima :) . Para esto tenemos otro tipo de Nodo en el Behavior
Tree, que son los Services. Service es el nodo que tenemos en el Behavior Tree para tener un proceso
ejecutándose contantemente cada un intervalo de tiempo determinado, y es exactamente este tipo de
nodo el que tenemos que usar para implementarle la tarea de vigilante a nuestro NPC.
En el Content Browser dentro de la carpeta AIEnemy crea un nuevo Blueprint que herede de
BTService_BlueprintBase y ponle de nombre CheckNearbyEnemy. Antes de editar este Blueprint,
tenemos que agregar una nueva entrada en el BlackBoard. Un nuevo “conocimiento“ que tendrá que tener
el NPC, y es el actor al que va a seguir cuando detecte que hay alguien en la zona.
Abre el Blackboard y agrega un nuevo Key de tipo Object y ponle de nombre TargetActorToFollow. Aquí
guardaremos la referencia a nuestro personaje protagónico cuando nos acercamos a la zona que vigila el
enemigo.
Abre el CheckNearbyEnemy y primero crea las siguientes variables que usaremos en este visualscript:
DesiredObjectTypes de tipo EObjectTypeQuery y en la sección Default Value del panel de Detalles de
esta variable, selecciona Pawn. En un segundo te explico donde la usaremos.
TargetActorToFollow: De tipo BlackBoardKeySelector y como ya hicimos en el Task que creamos, esta
variable la usaremos para obtener el valor de la clave con este nombre en el Blackboard. Esta variable
créala pública dando clic en el iconito del ojito que tiene a la derecha.
Te explico lo que tiene que hacer el algoritmo para que lo intentes crear por tu cuenta, de cualquier forma
te dejaré también la imagen de referencia.
Los nodos de tipo Service en el BT cuentan con el evento Event Receive Tick, este evento es muy
parecido al Tick que ya vimos en el tutorial pasado, solo que a diferencia de ese, este se ejecuta a un
intervalo fijo configurable. Lo que vamos ha hacer constantemente, gracias al evento Tick del Service, es
generar una esfera “imaginaria“ alrededor de todo el NPC mediante el Nodo Multi Sphere Trace for
Objects. Este es un método súper útil que nos da el UE4 y nos permite generar un esfera imaginaria en
una posición determinada con un radio que queramos y obtener todos los objetos que estén dentro de esa
zona de la esfera. Este método espera los siguientes parámetros:
Puntos de Inicio y fin de la línea que usará como referencia para generar la esfera, en este caso usamos
la posición del NPC como punto de inicio y el mismo punto en X y Y pero solo con un poco más de altura
para que sean distintos los puntos (si inicio y fin son iguales, no funcionará). Usamos la posición del NPC
para que siempre la esfera se genere alrededor de este personaje.
El parámetro Radio es bastante claro, es el radio de la esfera. Le damos un valor bastante grande y
definirá el alcance de vigilancia del enemigo a la redonda.
El parámetro Object Types es un arreglo que define los tipos de objetos que queremos tener en cuenta
para ese chequeo. En este caso queremos tener en cuenta a los objetos de tipo Pawn. Y para esto fue
que creamos la variable DesiredObjectTypes y le dimos el valor de Pawn por defecto. Fíjate que este
parámetro es un arreglo, podemos pasarle más de un tipo, pero en este caso solo queremos tener en
cuenta a los Pawns por eso es que creamos un arreglo de un solo elemento.
Trace Complex: No necesitamos tenerlo en true, se usa para chequeo en mallas de colisiones complejas,
pero requiere mayor consumo de recursos. Como este no es el caso de nosotros, lo podemos dejar en
false.
Actors To Ignore: Otro muy útil parámetro que recibe este método. Nos permite definir todos los actores
que queremos que ignore el método, o sea que aunque estén dentro de la esfera y sean un Pawn no se
van a devolver en el resultado. Y aquí lo usamos para ignorar el propio Pawn del enemigo. Lógico no ?, el
enemigo siempre va a estar dentro de la esfera pero él no será un resultado que nos interese.
Draw Debug Line es un muy útil parámetro que nos sirve para debugear la esfera en el nivel. Para esta
prueba ponlo en For Duration y podrás ver el comportamiento de esta esfera imaginaria en el nivel cuando
lo probemos.
Cada vez que se ejecute este método como resultado tendremos todos los actores que están dentro de la
esfera, y por tanto cercanos al enemigo o false si no se encuentra ninguno. Esto es lo que haremos en la
primera parte del algoritmo. Paso a paso sería:
1 – Casteamos el Actor que viene en el Tick a AIEnemyController que es el Controller de nuestro
enemigo.
2 – Obtenemos el Pawn de ese Controller con el nodo Get Controller Pawn.
3 – Obtenemos la posición del Pawn con el nodo Get Actor Location
4 – Creamos u nuevo vector a partir de la posición del Pawn incrementándole solo en el eje Z un poco.
Estas dos posiciones las pasamos como parámetro al nodo MultiSphereTrace for Objects. Además le
pasamos los otros parámetros que necesita. Fíjate que la línea blanca define el flujo de ejecución del
algoritmo.
Una vez que se ejecuta MultiSphereTrace for Objects tenemos en el puerto de salida Out Hits un arreglo
de objetos HitResults y en el puerto Return Value, true, si se encontró algún objeto y false en caso
contrario.
En este punto necesitamos una condición y para eso usamos el Nodo Branch que recibe el flujo de
ejecución (los puertos blancos) y permite separar el script en dos flujos según el resultado de la condición,
este nodo es el clásico IF de programación.
Si MultiSphereTrace for Objects retorna true quiere decir que encontró resultados. Entonces, continuamos
el flujo del programa con el nodo ForEachLoopWithBreak, este nodo como su nombre lo indica, es el
clásico for each de C++ que permite iterar un arreglo de elementos y detenerlo, llamando al break, cuando
se cumpla la condición que buscamos. Iteramos entonces todos los actores que se encontraron dentro de
la esfera. Fíjate que tenemos que conectar el puerto de salida Hits del MultiSphereTrace for Objects al
puerto de entrada Array del ForEachLoopWithBreak para hacerle saber a este cuál es el array que va a
iterar.
El puerto Loop Body del ForEachLoopWithBreak es el flujo del programa en cada iteración 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 muchísima información del punto de
interacción, 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. Fíjate que para obtener el Pawn usamos el
método Get Player Character que ya hemos usado en los tutoriales anteriores. Fíjate también que una vez
que setteamos el valor del TargetActorToFollow pasamos el flujo al Break del ForEach porque ya no
necesitamos más nada.
Bien, este es el caso donde se encuentra al personaje protagónico dentro de la esfera. Pero para el caso
en el que el Actor que se encuentre dentro de la esfera no sea el personaje protagónico o que el método
MultiSphereTrace for Objects retorne false, que quiere decir que no hay ningún 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 protagónico 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 parámetro y nos servirá aquí para ver el resultado de nuestro Service en
ejecución
Finalmente, el Blueprint te tendrá que quedar así:
Agregando el Service CheckNearbyEnemy al Behavior Tree.
Solo resta agregar este nuevo nodo al BT. Abre el AIEnemyBehaviorTree, elimina el Link entre el nodo
ROOT y el Sequence. Arrastra el borde inferior del ROOT y agrega un Selector nuevo. Da clic derecho
sobre ese nuevo selector/Add Service y selecciona CheckNearbyEnemy. Se te agregará dentro del
Selector. Selecciónalo y en el panel de Detalles la sección Default el atributo TargetActorToFollow (que es
la variable que le declaramos al blueprint como pública) selecciónale como valor TargetActorToFollow del
combo que hace referencia a las variables declaradas en el BlackBoard. Fíjate también que en la sección
Service podemos modificar el intervalo del Tick y un aleatorio de desviación, para que no siempre se
ejecute exactamente en un intervalo fijo. De momento podemos quedarnos con los valores por defecto.
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, después le quitas el valor que le dimos al parámetro 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)
Creando otro Task en el Behavior Tree para perseguir al personaje cuando el enemigo detecte que
está cerca.
Súper hasta ahora verdad !? :) … pero seguimos teniendo un enemigo un poco ”bobo” porque a pesar de
ya detectar que nos acercamos a su zona de seguridad, no hace nada, sigue sin inmutarse. Vamos a
solucionar esto, y para ello necesitamos otro Task. Como dijimos, los Task son para ejecutar acciones
concretas y es exactamente eso lo que queremos. Cuando el enemigo nos detecte, que nos persiga.
Vamos al Content Browser, en la carpeta AIEnemy y crea un nuevo Blueprint que herede de
BTTask_BlueprintBase y dale de nombre MoveToEnemyTask. Ábrelo para editarlo y agrega la variable
TargetActorToFollow de tipo BlackBoardKeySelector y hazla pública. Después crea el siguiente algoritmo.
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 método SUPER
GENIAL que nos da el UE4, el nodo AI Move To. Con este método 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 parámetros el Pawn, un Destino que será un vector fijo u otro Actor que
será al que perseguirá el Pawn pasado en el primer parámetro. 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ó. Fíjate que aquí también 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 estás loc@ por conectar este Task al árbol y probar, pero tenemos un
pequeño detalle. Este Task solamente lo podemos ejecutar si la variable TargetActorToFollow tiene valor
en el BlackBoard, porque como vimos si el CheckNearbyEnemy determina que el personaje no está
cerca, deja en NULL está variable y entonces no tiene porqué ejecutarse esta rama del árbol de
comportamiento, sino que pasa a la sección de vigilante.
Para ejecutar condiciones en el Behavior Tree que determinen si continuar ejecutando una rama
determinada del árbol tenemos otro tipo de nodo que son lo Decorators. Vamos entonces a crear un
decorator para comprobar si la variable está seteada, y si es así, entonces se puede ejecutar este Task,
sino, pasa a la otra rama del árbol.
Arrastra del borde inferior del CheckNearbyEnemy y crea un nuevo Selector, dale clic derecho y
selecciona Add Decorator y selecciona Blackboard. En este caso no vamos a crear un Decorator
personalizado, vamos a usar como mismo usamos el Task Wait y Move To que ya vienen con el UE4, el
decorator Blackboard, que nos permite comprobar el valor de un key en el BlackBoard. Selecciona el
decorator y en la sección Flow Control de panel de Detalles, en el atributo Observer aborts selecciona
Both. Este parámetro define como se comportará el árbol cuando no se cumpla la condición. En este caso
lo que queremos es que inmediatamente que TargetActorToFollow esté en NULL se pase a ejecutar la
otra rama, para que continúe como vigilante. Prueba cambiar después este valor a Self para que notes la
diferencia del comportamiento.
En la sección Blackboard, en el atributo BlackBoard Key, selecciona TargetActorToFollow para definirle
que este es el key del blackboard que queremos comprobar y en Key Query selecciona Is Set para
definirle el tipo de condición que queremos comprobar sobre el TargetActorToFollow.
Por último arrastra del selector que tiene este Decorator y conecta el Task MoveToEnemyTask,
selecciónalo y en la sección Default, en el atributo Target Actor To Follow selecciona
TargetActorToFollow.
Listo ¡!! Guarda, compila y ejecuta el juego y muévete cerca del enemigo, cuando te vea corre rápido para
que te le alejes, verás 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 próximos
tutoriales haremos algo más interesante.
Conclusión
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 más 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.
En el próximo tutorial vamos a continuar con este ejemplo implementado los mecanismos para que este
enemigo nos haga algo cuando llegue a nosotros. También le dedicaremos un tiempo a ver esta misma
implementación pero desde C++, y así analizamos las dos variantes ;)