[XNA] Comportamientos de navegación para agentes autónomos (steering behaviors)

Los comportamientos de navegación, aplicados a la programación de juegos, se refieren a la capacidad de los agentes de desplazarse de forma autónoma, es decir, que el comportamiento de los mismos no está previamente calculado, sino que es generado según las condiciones del entorno en cada momento, de forma espontánea. El código de estos ejemplos es relativamente sencillo, pero dado que no he encontrado ninguna implementación en XNA de este tipo de algoritmos, me ha parecido interesante compartirlos.

Pueden existir muchos comportamientos de este tipo (de hecho, podemos inventarnos los que queramos), pero hay algunos que pueden llegar a considerarse como más “estandarizados”. Cuando digo estandarizados, me refiero a los comportamientos, no a su implementación: no existe una fórmula universal para codificarlos. Los comportamientos que veremos en esta primera parte son:

  • Búsqueda
  • Huida
  • Errar

Lo realmente interesante es que el código es muy sencillo, y se pueden conseguir resultados muy interesantes introduciendo variaciones del mismo en nuestras producciones.


La implementación

La codificación la he planteado de forma que existe una posible lógica de comportamientos autónomos, a la cual se le envía una estructura de tipo Agente, que contiene cierta información de referencia del mismo. Esta estructura es la siguiente:

public struct Agente
{
   public Vector2 posicion;
   public Vector2 velocidad;
   public float velocidadMaxima;
   public float angulo;
   public float rotacionMaxima;
   public Estado estado;
   public float distanciaMinima;
}

Conocer esta estructura va a ser útil a continuación, para entender el código que implementa cada uno de los comportamientos.


Búsqueda y huida

La búsqueda es el reverso de la huida. Así pues el código será prácticamente idéntico. Vamos a verlo:

        public void Seek(GameTime gameTime)
        {
            if (Vector2.Distance(agente.posicion, this.destino) > 10.0f)
            {
                agente.velocidad = destino – agente.posicion;
                agente.velocidad.Normalize();
                agente.velocidad *= agente.velocidadMaxima;

                agente.angulo = (float)System.Math.Atan2(agente.posicion.Y – this.destino.Y, agente.posicion.X – this.destino.X);

                Vector2 heading = new Vector2(
                    (float)Math.Abs(Math.Cos(agente.angulo)), (float)Math.Abs(Math.Sin(agente.angulo)));

                agente.posicion += heading * agente.velocidad;
            }           
        }

Este método Seek corresponde a la implementación de la búsqueda. Para implementar la huida, sólo sería necesario cambiar la forma de establecer la velocidad a:  agente.velocidad = destino – agente.posicion;
No tiene demasiado secreto, establecemos la velocidad a partir de la posición actual del agente y su destinación, y después calculamos el ángulo y la dirección sobre la cual debe rotar el agente (mirará siempre hacia el vector posición “destino”). 


Movimientos erráticos

Este método es un poco más complicado, y ha sido inspirado en el algorismo de Craig Reynolds, aunque quiero recalcar que no es una implementación explícita de su propuesta.

public void Wander(GameTime gameTime)
        {
            if (tiempo == 0.0f || ultimoUpdate >= tiempo)
            {
                tiempo = Randomizer.obtenirAleatori(500, 2000);
                int radioCirculo = 25;
                float anguloTMP = Randomizer.obtenirAleatori(-360, 360);

                float posicionXCirculo = agente.posicion.X + 50.0f + ((float)Math.Cos(anguloTMP) * radioCirculo);
                float posicionYCirculo = agente.posicion.Y + 50.0f + ((float)Math.Sin(anguloTMP) * radioCirculo);

                // Si el ángulo es negativo, invertimos la dirección
                if (anguloTMP < 0)
                {
                    posicionXCirculo *= -1;
                    posicionYCirculo *= -1;
                }

                this.destino = new Vector2(posicionXCirculo, posicionYCirculo);
                ultimoUpdate = 0.0f;
            }

            if (Vector2.Distance(agente.posicion, this.destino) > 10.0f)
            {
                agente.velocidad = destino – agente.posicion;
                agente.velocidad.Normalize();
                agente.velocidad *= agente.velocidadMaxima;

                agente.angulo = (float)System.Math.Atan2(agente.posicion.Y – this.destino.Y, agente.posicion.X – this.destino.X);

                Vector2 heading = new Vector2(
                    (float)Math.Abs(Math.Cos(agente.angulo)), (float)Math.Abs(Math.Sin(agente.angulo)));

                agente.posicion += heading * agente.velocidad;
            }

            ultimoUpdate += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
        }

Una de las cosas interesantes que pueden verse en el algoritmo es que este se parece mucho al de búsqueda, pero con un añadido: el establecimiento de un destino aleatorio. La dirección destino se calcula aleatoriamente en un círculo “imaginario”, obteniendo uno de sus puntos por azar, lo cual se hace específicamente con estas líneas de código:

float posicionXCirculo = agente.posicion.X + 50.0f + ((float)Math.Cos(anguloTMP) * radioCirculo);
float posicionYCirculo = agente.posicion.Y + 50.0f + ((float)Math.Sin(anguloTMP) * radioCirculo);

Otra cosa “curiosa”, es que sólo recalculamos la dirección cada cierto tiempo, para no generar cambios en la dirección demasiado bruscos o ireales dentro de un juego.

Resultado final

Este es el resultado final de la implementación del algoritmo:

[View:http://www.youtube.com/watch?v=GMhxUllEaLY:550:0]

El código está disponible, como siempre, y puede ser descargado en este enlace.

Algunas ideas

¿Os imagináis utilizar estos algoritmos combinados con los de flocking del artículo anterior? Con la unión ambos algoritmos puede resultar fácil hacer una patrulla (flocking) que siga a su comandante, que se mueve de forma errática (wandering).

 

4 comentarios en “[XNA] Comportamientos de navegación para agentes autónomos (steering behaviors)”

Deja un comentario

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