Muy buenas!
Hoy vamos a explicar funcionalidad básica de los NavMesh para realizar una AI que busque un objetivo. En este caso, dicho objetivo serán un grupo de nodos custom (que los marcaremos como de destino sólo para evitar que generen caminos automáticamente y utilizarlos sólo como referencias de un Waypoint). Igualmente, describiré la forma de realizar un nuevo estado que persiga al jugador. Del mismo modo, de manera sencilla, se pueden generar otros estados y cambiar entre unos y otros mediante Kismet y con lo aprendido a la hora de crear eventos y lanzarlos (y por supuesto de crear nuevos estados).
Sin más, procedemos a ver el código para nuestros nodos custom a seguir por la patrulla:
Como se puede ver en el código, estos nodos no son más que NavigationPoint que no son calculados cuando compilamos caminos gracias a su flag “bDestinationOnly”. Podremos, desde la pestaña de Actors en el ContentBrowser (recordemos, botoncillo con logo de UDK en la barra superior de herramientas) arrastrar o crearlos y colocarlos en la escena seleccionándolos y botón derecho del ratón-> Crear aquí.
Es importante que nos fijemos en sus propiedades con F4 para saber su nombre (pestaña Object) porque después tendremos que asignárselo en el orden que deseemos al crear en camino de patrulla a seguir. Este método es un poco engorroso pero creo que, igual que ocurre con el motor Unity, resulta mejor a la hora de trabajar junto a diseñadores y sin necesidad de tocar código para poder cambiar diferentes patrullas de nuestros enemigos. Por supuesto, existen otras formas de realizarlo, aunque yo haya elegido esta como la que considero más sencilla para mi alumnado.
Acto seguido, pasamos a crear un UKNAIPawn que serán los pawns de nuestros enemigos. Particularmente, si deseamos tener varios tipos de enemigos con funcionalidades iguales (Exceptuando estética) sin necesidad de tener que crear una clase para cada uno de ellos, podremos trabajar con Arquetipos. Mi intención es explicarlos en una entrada más adelante. De momento, ahí va el código de nuestro peón para nuestra AI que patrulla:
//It’s controller MUST be spawned in order to Possess it! SpawnDefaultController();}defaultproperties{ ControllerClass=class’UKNGameInfo.UKNAIController’ // Ground speed of the pawn UserGroundSpeed=300.f // Set physics to falling Physics=PHYS_Falling // Remove the sprite component as it is not needed Components.Remove(Sprite) // Create a light environment for the pawn Begin Object Name=MyLightEnvironment bSynthesizeSHLight=true bIsCharacterLightEnvironment=true bUseBooleanEnvironmentShadowing=false End Object Components.Add(MyLightEnvironment) LightEnvironment=MyLightEnvironment // Create a skeletal mesh component for the pawn Begin Object Name=MySkeletalMeshComponent bCacheAnimSequenceNodes=false AlwaysLoadOnClient=true AlwaysLoadOnServer=true CastShadow=true BlockRigidBody=true bUpdateSkelWhenNotRendered=false bIgnoreControllersWhenNotRendered=true bUpdateKinematicBonesFromAnimation=true bCastDynamicShadow=true RBChannel=RBCC_Untitled3 RBCollideWithChannels=(Untitled3=true) LightEnvironment=MyLightEnvironment bOverrideAttachmentOwnerVisibility=true bAcceptsDynamicDecals=false bHasPhysicsAssetInstance=true TickGroup=TG_PreAsyncWork MinDistFactorForKinematicUpdate=0.2f bChartDistanceFactor=true RBDominanceGroup=20 Scale=1.f bAllowAmbientOcclusion=false bUseOnePassLightingOnTranslucency=true bPerBoneMotionBlur=true End Object Mesh=MySkeletalMeshComponent Components.Add(MySkeletalMeshComponent)}
Como vemos, nuestro Pawn controlado por AI va a contener un array que contiene elementos de nuestra clase Custom de nodos (patrolPoints). Igualmente tenemos una variable que utilizaremos para detectar al player cuando se encuentre a una determinada distancia (para poder configurarlo en el Editor tiene (NombreDePestaña) en la variable. Ojo a la función PostBeginPlay, donde se spawnea su controlador y éste posee a este Pawn (SpawnDefaultController). También recordad que los valores de la malla del personaje los podéis cambiar a vuestro gusto. Estos son los que utilicé yo para algunas pruebas, con lo cual, seguramente sea necesario modificarlos para vuestro proyecto.
Por último, necesitaremos crear la clase más importante: El Controlador. Aquí es donde desarrollaremos nuestra máquina de estados y donde se hace uso de la NavMesh para poder ser utilizado. Coloco como siempre el código y después procedo a explicar. Recordad que tenéis comentarios en el propio código (no lo copiéis y peguéis sin más o puede que no comprendais correctamente su funcionamiento):
dónde colocamos las directrices que debe seguir nuestra clase NavigationHandler, encargada de encontrar los caminos. Para ello, debemos configurarla para que sepa de qué modo debe encontrar ese camino y cual es su “meta”.
function bool FindNavMeshPath()
{
//Clear cache and constraints. You need Path constraints to modify behaviour and a PathGoal
{
//Clear cache and constraints. You need Path constraints to modify behaviour and a PathGoal
//More information about Path constraints evaluators and Goals here:
NavigationHandle.PathConstraintList = none;
NavigationHandle.PathGoalList = none;
NavigationHandle.PathConstraintList = none;
NavigationHandle.PathGoalList = none;
//Create constraints
class’NavMeshPath_Toward’.static.TowardGoal(NavigationHandle,Player);
class’NavMeshGoal_At’.static.AtActor(NavigationHandle,Player,25);
class’NavMeshPath_Toward’.static.TowardGoal(NavigationHandle,Player);
class’NavMeshGoal_At’.static.AtActor(NavigationHandle,Player,25);
//Find Path
return NavigationHandle.FindPath();
}
Básicamente, utilizamos las clases estáticas de las constraints que modificarán el comportamiento de NavigationHandle a la hora de encontrar un camino (método estático FindPath). En este caso, el goal indicado es un Actor que lo he llamado Player (class’NavMeshGoal_At’.static.AtActor(NavigationHandle,Player,25);). En el caso de las patrullas, deberemos indicar aquí cual es el siguiente nodo a seguir.return NavigationHandle.FindPath();
}
Por supuesto, en el estado Patrol, se comprueba si hemos alcanzado el nodo que debemos seguir ( if(Pawn.ReachedDestination(currentPathNode)) //Hemos alcanzado el nodo ) y modificar currentPathNode al siguiente que debemos seguir en nuestra patrulla. En este caso, cuando llega al nodo final, el Pawn dará media vuelta y hará el camino contrario. Una vez más, os indico que ésta es una manera de hacer las cosas y que deberéis realizar los cambios pertinentes entendiendo su funcionamiento para que se adecúe a las necesidades de vuestro proyecto.
Antes de poner en marcha todo lo realizado, nos queda realizar trabajo en el editor. Básicamente debéis colocar un NavMesh Pylon y arrastrarlo a la escena. Estos pilares poseen un radio y dicho radio puede configurarse para ser más amplio. Varios pilones pueden convivir confluyendo sus radios de acción para combinarse entre si y abarcar una mayor superficie en un mapa determinado, pero NUNCA debemos colocar un pilar dentro del radio de acción de otro pilar.
Configuración de NavMesh (clase Scout.uc)
Los NavMesh siguen una serie de pautas para su creación mediante otra clase llamada Scout. Para poder configurar cómo se genera para su utilización, debemos crear nuestra propia clase Scout con los valores que deseemos (Su utilidad, por ejemplo, es la de configurar si diferentes tamaños de Pawn de altura van a poder o no moverse dependiendo de por qué lugares):
Creamos una clase de Scout:
Class NULLScout extends Scout;
defaultproperties
{
//Limpiamos cualquier path que hubiera anteriormente ya que utilizaremos la altura y el ancho
//de nuestro Pawn para poder utilizarlo
PathSizes.Empty
PathSizes.Add((Desc=Human,Radius=180,Height=330))
//Vemos que se pueden incluir en PathSize varios conjuntos de radios y altura con una //descripción
NavMeshGen_EntityHalfHeight=165
//Restamos a este Offset a MaxPolyHeight para saber el bounding final del NavMesh en altura
NavMeshGen_StartingHeightOffset=140
//Un número mayor que la altura de nuestro pawn
NavMeshGen_MaxPolyHeight=175
}
Una vez creada esta clase, debemos ir al fichero de configuración DefaultEngine.ini (UDKGame/Config) y cambiar
[Engine.Engine]
ScoutClassName=NULLGame.NULLScout
Volvemos al editor y recompilamos los paths (el botoncito con nodos con forma de K) y veremos como la generación de la malla del NavMesh se ha modificado.
Colocando los PathNodes y testeando
Una vez realizado esto, podéis colocar vuestros custom nodes en la escena y arrastrar a la misma un actor del tipo de vuestro enemigo. Allí deberemos acceder a sus propiedades y añadir los pathnodes a seguir. Para ello, podemos bloquear con el icono arriba a la derecha, las propiedades de nuestro Pawn. Así, podremos seleccionar objetos en la escena sin que nos cambie a sus propiedades en esta ventana.
Una vez hecho esto, podemos pasar a ir colocándolos con el iconito de flecha o + dentro del array dinámico sin problemas. La otra opción, más tediosa es comprobar el nombre de la instancia de cada nodo y escribir en su lista de nodos custom (la del Pawn) cada uno de ellos siguiendo un patrón como este:
UKNPathNode’NombreDelMapa.TheWorld:PersistentLevel.UDKPathNode_NUMERODEINSTANCIA’
Fijáos bien si tenéis diferentes niveles cargados a la vez, ya que la parte de PersistentLevel seguramente cambie según el Level en el que se encuentre. Seguramente esta información la amplíe en un futuro con una explicación más gráfica de esta última parte.
ENLACES IMPORTANTES:
Funciones latentes dentro de la etiqueta Begin,
http://udn.epicgames.com/Three/MasteringUnrealScriptStates.html#LATENT%20FUNCTIONS
Constraints y Goal Evaluators
http://udn.epicgames.com/Three/NavMeshConstraintsAndGoalEvaluators.html
Documento técnico de Animation Mesh
http://udn.epicgames.com/Three/NavigationMeshTechnicalGuide.html
ENLACES IMPORTANTES:
Funciones latentes dentro de la etiqueta Begin,
http://udn.epicgames.com/Three/MasteringUnrealScriptStates.html#LATENT%20FUNCTIONS
Constraints y Goal Evaluators
http://udn.epicgames.com/Three/NavMeshConstraintsAndGoalEvaluators.html
Documento técnico de Animation Mesh
http://udn.epicgames.com/Three/NavigationMeshTechnicalGuide.html