PFM: Navegación de personajes en UDK – Pathfinding y NavigationMesh

Hasta ahora el PNJ que hemos introducido en el juego tiene un comportamiento bastante… estúpido. Nos sigue sí, pero no sabe sortear obstáculos. En esta entrada le daremos algo más de inteligencia (no mucha) para que se mueva por el escenario con más habilidad. Para la navegación de los personajes por el mapa, UDK provee de 2 sistemas diferentes.

Por un lado podríamos usar el clásico sistema de PathNodes, basado en una serie de puntos colocados en el mapa que crean una malla de movimiento por la que se mueven los personajes. Este sistema es usado en Unreal Tournament y aunque efectivo, tiene la desventaja de tener que colocar manualmente los nodos, modificando su posición si se modifica el mapa.

Por otro lado UDK nos ofrece un sistema de implementación más reciente que se basa en calcular una malla por la que el personaje puede desplazarse de forma más libre, llamada NavigationMesh. La ventaja de este sistema es que se genera automáticamente teniendo en cuenta la geometría del escenario. Este es el sistema que usaremos.

Imprescindible para entender todo: http://udn.epicgames.com/Three/AIAndNavigationHome.html

Es una documentación bastante completa (aunque no perfecta como veremos luego)

De nuevo también son muy útiles los tutoriales de Cédric Hauteville: http://www.moug-portfolio.info/udk-ai-pawn-movement/

Veamos cómo crear el NavigationMesh en nuestro mapa y que los enemigos lo utilicen para dirigirse a un objetivo, que será su labor en el juego, como vimos en la mecánica.

PFMEnemyTarget

En primer lugar vamos a crear una pequeña clase llamada PFMEnemyTarget, que representará el destino de los enemigos. Colocaremos este objeto en el mapa y los enemigos deberán dirigirse hacia él. El código es muy sencillo:


class PFMEnemyTarget extends Actor
placeable;

defaultproperties
{
    // Sprite para verlo en el editor
    Begin Object Class=SpriteComponent Name=Sprite
        Sprite=Texture2D'EditorResources.LookTarget'
        HiddenGame=False
    End Object
    Components.Add(Sprite)
}

Una vez compilado correctamente, podremos encontrar nuestro PFMEnemyTarget en el Content Browser y colocarlo en un punto del mapa. Por el sprite que hemos escogido, se verá representado como una pequeña diana:

pfm08_01Para esta prueba he creado un mini laberinto en el editor y he colocado el objetivo al final del mismo. Los enemigos podrán navegar por el laberinto y cumplir su misión de alcanzar el PFMEnemyTarget:

pfm08_02

Creación del NavigationMesh

Crear el NavigationMesh en el editor es bastante sencillo. Sólo debemos buscar en los actores del Content Browser el llamado Pylon (en Actors > Navigation Point > Pylon) y colocarlo en el mapa:

pfm08_03Modificando sus propiedades podemos ajustar el radio a un tamaño que abarque todo el mapa. Este radio determinará el área que tendrá en cuenta para generar el NavigationMesh:

pfm08_07Hecho esto pulsaremos el botón Build Paths (pfm08_04), lo que generará el Navigation Mesh.

Para visualizar el NavigationMesh generado basta con pulsar la tecla P:

pfm08_05Las partes verdosas indican superficies por las que se puede caminar, mientras que las rojas indican obstáculos:

pfm08_06Es todo, el NavigationMesh está creado. Ahora hay que hacer que nuestros personajes lo utilicen para moverse por el escenario.

PFMEnemyController

Para ello vamos a modificar el controller, que es donde reside toda la ‘inteligencia’ de los enemigos. El funcionamiento es sencillo. Una vez obtenido el objetivo, se irán haciendo peticiones de PathFinding desde la posición actual al objetivo, lo que le indicará al controller el camino óptimo para llegar al PFMEnemyTarget.


class PFMEnemyController extends AIController;

var Actor FinalTarget;

var Vector NextDest;      // Siguiente punto del camino (Pathfinding)

// Evento llamado cuando el Controlador posee a un Pawn
event Possess(Pawn inPawn, bool bVehicleTransition)
{
    super.Possess(inPawn, bVehicleTransition);
    Pawn.SetMovementPhysics();
}

// Estado inicial
auto state Init
{
    local PFMEnemyTarget et;

Begin:
    foreach DynamicActors(class'PFMEnemyTarget', et)
    {
        FinalTarget = et;
    }

    if(FinalTarget != none)
    {
        // Objetivo encontrado
        GoToState('GoToFinalTarget');
    }
    else
    {
        // No se encuentra objetivo
        GoToState('Idle');
    }
}

state Idle
{

}

state GoToFinalTarget
{
    // Calcula el camino al objetivo
    function bool FindNavMeshPath()
    {
        // Clear cache and constraints (ignore recycling for the moment)
        NavigationHandle.PathConstraintList = none;
        NavigationHandle.PathGoalList = none;

        // Create constraints
        class'NavMeshPath_Toward'.static.TowardGoal( NavigationHandle, FinalTarget );
        class'NavMeshGoal_At'.static.AtActor( NavigationHandle, FinalTarget, 32 );

        // Find path
        return NavigationHandle.FindPath();
    }

Begin:
    if( NavigationHandle.ActorReachable(FinalTarget))
    {
        // Si se puede llegar directamente hacerlo:
        MoveTo(FinalTarget.Location, FinalTarget, 32);
        GoToState('AtFinalTarget');
    }
    else if (FindNavMeshPath())
    {
        NavigationHandle.SetFinalDestination(FinalTarget.Location);

        if(NavigationHandle.GetNextMoveLocation(NextDest, Pawn.GetCollisionRadius()*5))
        {
            // Ir al siguiente punto del camino
            MoveTo(NextDest,none,128.0);
        }
        else
        {
            // No hay punto siguiente
            MoveToward(FinalTarget);
        }
    }
    else
    {
        // No se encuentra camino posible
        MoveToward(FinalTarget);
    }
    goto 'Begin';
}

state AtFinalTarget
{

}

defaultproperties
{
    MaxMoveTowardPawnTargetTime=1       // Tiempo que MoveToward tarda en hacer return
}

Si probamos ahora el juego, los PFMEnemies se moverán por el laberinto y llegarán hasta el objetivo antes o después. Sin embargo, al menos en mi caso, se atascan mucho en las esquinas y les cuesta tomarlas…por qué será… (he aquí por lo que decía que la documentación no es perfecta). Tras horas rebanándome los sesos encuentro por un foro una referencia a una clase llamada Scout, de la cual en la documentación no se hace mención alguna. Resulta que en esta clase se define el espacio que deben dejar con las paredes los personajes que utilicen los NavigationMesh. De esta manera puede especificarse que no tomen las esquinas tan ajustadas, consiguiendo así que no se atasquen tanto… Definamos pues nuestro propio Scout…

(NOTA: En pruebas posteriores me di cuenta que a veces el NavigationMesh deja de funcionar sin motivo aparente o al cambiar de nivel. Deshaciendo el camino he comprobado que usando el Scout original estos fallos parecen no darse y por lo que parece hacer un Scout propio es algo que lleva tiempo dando problemas en UDK. Por tanto, y sin que sirva de precedente, en este caso será mejor modificar el propio UTScout, en lugar de crear el propio. Dejo cómo hacerlo por si alguna vez arreglan este bug)

PFMScout

class PFMScout extends UDKScout;

// Propiedades copiadas de UTScout, salvo modificaciones
defaultproperties
{
    Begin Object NAME=CollisionCylinder
        CollisionRadius=+0034.000000
    End Object

    PathSizes.Empty
    PathSizes(0)=(Desc=Crouched,Radius=22,Height=29)
    PathSizes(1)=(Desc=Human,Radius=22,Height=44)
    PathSizes(2)=(Desc=Small,Radius=140,Height=44)     //    PathSizes(2)=(Desc=Small,Radius=72,Height=44)
    PathSizes(3)=(Desc=Common,Radius=140,Height=44)    //   PathSizes(3)=(Desc=Common,Radius=100,Height=44)
    PathSizes(4)=(Desc=Max,Radius=140,Height=100)
    PathSizes(5)=(Desc=Vehicle,Radius=260,Height=100)

    TestJumpZ=322
    TestGroundSpeed=440
    TestMaxFallSpeed=2500
    MaxStepHeight=26.0
    MaxJumpHeight=49.0
    MaxDoubleJumpHeight=85.0
    MinNumPlayerStarts=1
    WalkableFloorZ=0.78

    PrototypePawnClass=class'UTGame.UTPawn'
    SizePersonFindName=Human

    NavMeshGen_EntityHalfHeight=44.0
    NavMeshGen_StartingHeightOffset=44.0
}

Básicamente es el UTScout que se usa por defecto copiado y modificado. Los PathSizes determinan, como indica el nombre, el ancho de los caminos que se generan en el NavigationMesh. En el editor pueden verse estos PathSizes, pues son esas líneas horizontales que van de unas esquinas a otras del NavigationMesh. Haciendo que el radio sea mayor se consigue que los personajes no se peguen tanto a las paredes, evitando que se atasquen tanto.

DefaultEngine.ini

Hay que modificar el fichero DefaultEngine.ini para indicarle al motor que utilice nuestro Scout específico.

[Engine.Engine]
ConsoleClassName=UTGame.UTConsole
ScoutClassName=PFM.PFMScout

Para probar el resultado hay que entrar al editor y generar de nuevo el NavigationMesh.

Y ya por fin, los enemigos se desplazan razonablemente bien:

En algunos puntos se siguen atascando un poco pero bueno, no está mal como comienzo 😛
Banner Blog

Anuncios

13 pensamientos en “PFM: Navegación de personajes en UDK – Pathfinding y NavigationMesh

  1. ¿Hay algun tipo de limitación en el escenario? Me refiero si funciona para cualquier tipo de terreno o edificación con varias plantas

    • Con terrenos no lo he probado, pero para un mapa creado con geometría BSP y modelos de mallas estáticas no hay en principio ninguna limitación. Incluso puede usarse con modelos móviles como plataformas o ascensores.

  2. Saludos de nuevo xD… en este tambien me salio todo bien la parte de la programacino .. pero a lahora de poner el SpawnEnemy se aparecen al lado mio….. y empiezan a correr derechito a la mira… pero no se pasan laspaardes… llegan a cierta parte cerca de la mira pero como hay una pared se quedan todos hay mirandola…..

    eso es todo xD ….

    • Si avanzan directamente es que el navigation mesh es erróneo o no existe. Comprueba en el editor que el Navigation Mesh se genera correctamente y abarca la superficie de tu mapa.

    • jajajaj ya me sirvio fue un error tonto xD……

      habia puesto a funcionar primero el navigator…. y despues hize lso muros para q pasaran por ellos … pero como no lo actualize al mapa q habia creado despues no lo habia tomado….. pero ya andan por donde deben andar despues de volver a crear otro ya con el mapa realizado…. ya las zonas verdes y rojas habian cambiado

      Gracias por tu paciencia >_O…

  3. ¿Se puede hacer que los enemigos giren más rápido a la hora de dirigirse a un objetivo?

    Me encuentro con el problema siguiente: me acerco a los enemigos, que están a su bola, por la espalda. Les golpeo y en ese momento activo el huntplayer state usando el pawn que les golpeó (lo que hace que se puedan perseguir y golpear entre ellos).

    Pero cuando el enemigo se orienta hacia mí lo hace muy despacio. He estado buscando una variable de velocidad de rotación por ahí pero no he encontrado nada.

    • Modifica en el Pawn la variable RotationRate. En defaultproperties:

      RotationRate=(Pitch=40000,Yaw=40000,Roll=40000)

      La componente que tienes que cambiar es Yaw.

  4. Funciona, gracias. Estoy haciendo la IA de los enemigos, que pueden estar inicialmente parados, en una animación o moviéndose aleatoriamente por el mapa. Y al verte o atacarles reaccionan defendiéndose, huyendo o persiguiéndote.
    Bueno, la mitad de lo que acabo de decir no funciona bien todavía 😛

    Lo que estoy pensando es hacer las animaciones de golpear sólo de cintura para arriba, para que puedan golpear mientras están corriendo. Tal y como está, tienen que pararse para iniciar el golpe, y para el jugador basta con estar andando constantemente para que nunca le alcancen.

    También les he puesto ragdoll al morir y queda bastante bien. Con “Lifespan = 0” los cuerpos se mantienen hasta que desaparecen del plano de la cámara. Al volver ya no están, pero por lo menos no se ven desaparecer.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s