[IA] Obstacle Avoidance: Steering Behaviors en XNA (II de II)

En el episodio anterior hablé de algunos steering behaviors básicos. Hoy hablaré de un Steering que puede solaparse a cualquiera de los anteriores, consiguiendo efectos de IA más avanzados: el Obstacle Avoidance. La idea es que podamos aplicar este comportamiento a cualquiera de los steering behaviors que ya tenemos desarrollados, de forma que cualquiera de nuestros agentes evite las colisiones con los obstáculos existentes en el «mundo» en que se desenvuelve.

Para ello nos basaremos de nuevo en los principios de Craig Reynolds, en los que define un pseudoalgoritmo efectivo para aproximar los obstáculos mediante círculos, y evitar las colisiones con los mismos mediante la proyección de un rectángulo en frente de nuestro agente (orientado hacia la dirección de avance), que testeará en cada momento posibles intersecciones entre dicho rectángulo y los obstáculos, identificando así posibles colisiones entre el agente y estos obstáculos en un «futuro» cercano. Lo malo es que el amigo Craig no nos facilita el código… sólo nos da sus mandamientos. Para ver buenos ejemplos de código tenemos la librería OpenSteer, SharpSteer, o uno de mis libros favoritos y reciente adquisición: Programming Game AI by Example. Con todo esto y un poco de imaginación vamos a conseguir lo siguiente:

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

Si vísteis mi artículo anterior de steering behaviors, la diferencia está en los obstáculos con forma de circunferencia, que están repartidos por el «mundo», y que los agentes los esquivan, o nunca pasan por encima de ellos. ¿Cómo conseguimos esto? Con la librería que he desarrollado, en la clase Game solo habría que añadir lo siguiente:

bichoPursuit.behaviors.AddBehavior(new Pursuit(Arrive.Deceleration.normal, 50.0f));
bichoPursuit.behaviors.AddBehavior(new ObstacleAvoidance(ref gameWorld, 15.0f));

Lo cual quiere decir que al agente «bichoPursuit», le damos primero el behavior de pursuit, y el de obstacleAvoidance. Obviamente los círculos no se renderizarian en un juego… y serían sustitidos por los sprites 2D o modelos 3D correspondientes, aquí los uso solo para desarrollar y debugar la librería. Y con esto ya está! Bueno… lógicamente en la clase que hay por detrás he hecho un trabajo extra, implementando el nuevo steering en la clase ObstacleAvoidance:

Es muy parecida a las herederas de Steering que ya hemos visto, tiene su método «CalculateSteering». La diferencia ahora está en el uso que hace de la clase Circle para definir los obstáculos en forma de circunferencia (en una librería completa habría que definir otras formas geométricas para los obstáculos, la más flexible sería la línea). Tambien observamos un método nuevo: EnforceNonPenetrationConstraint, después veremos qué hace. La clase Circle, a pesar de ser sencilla nos va a dar mucho juego. Nunca mejor dicho 😛

CalculateSteering

Este método contiene la «chicha». Lo primero que hará será buscar todos los obstáculos próximos al agente y guardarlos en una lista. Con esto evitaremos comprobaciones excesivas con todos los obstáculos que pudieran haber en el juego, y por tanto evitaremos perder valiosísimo tiempo del procesador. Una vez tenemos identificados los obstáculos, calcularemos la distancia a partir de la cual debemos comprobar/reaccionar ante las posibles colisiones con los obstáculos circulares.

A continuación recorremos todos los obstáculos que se encuentran en el radio de acción que hemos identificado anteriormente, y dependiendo de la distancia (o incluso penetración) del agente sobre el obstáculo, generaremos una steering force proporcional hacia la dirección que se considere oportuno.

Compensación de fuerzas

El uso en paralelo de varios steering behaviors puede provocar que un steering de Flee quiera alejar al agente de su perseguidor, por ejemplo hacia la derecha, pero hacia la derecha podría estar el círculo… así pues tenemos un conflicto. Este conflicto se puede superar de varias formas… pero la más sencilla (y no necesariamente la mejor) mediante compensación de fuerzas, esto es, dando un peso específico a cada steering sobre el total, de forma que este sea tenido en cuenta para calcular el resultado final:

En la clase SteeringBehavior tendríamos esto:

if (this.HasBehavior(BehaviorType.seek))
   this._steeringForce += this._seek.CalculateSteering(entity) * this.weightSeek;

if (this.HasBehavior(BehaviorType.obstacle_avoidance))
   this._steeringForce += this._obstacleAvoidance.CalculateSteering(entity) * this.weightObstacleAvoidance;

Donde hay que destacar la multiplicación que se hace sobre la steeringForce contra su peso establecido de forma constante en este caso (pero podría ser variable según las situaciones).  Como no podemos predecir con exactitud los resultados que van a producir la compensación de fuerzas, puede darse -y se da- la situación de que un agente termine pasando por encima de un obstáculo. Para evitarlo, el behavior ObstacleAvoidance llamará al método EnforceNonPenetrationConstraint antes de devolver el resultado calculado del steering. Esta función corregirá la posición de la entidad para asegurar que no se solapa sobre ningún obstáculo.

La verdad es que después de desarrollar este ejemplo me voy a pensar si saco o no una tercera parte… los steering behaviors dan mucho de sí… y existen otros comportamientos muy interesantes: evitar obstáculos que sean líneas, ocultarse, bloquear, asegurarse que los agentes no se solapan entre sí…

Os dejo el código en vuestras manos, tratádmelo bien! 😀

4 comentarios sobre “[IA] Obstacle Avoidance: Steering Behaviors en XNA (II de II)”

  1. Hola,quiero felicitarte por el articulo, en realidad estoy tratando de emplearlo de forma optima a un proyecto de la universidad que tengo. una duda resulta que pude hacer varios «objetos pursuit» y perfecto, siguen al heroe del juego, pero con el tiempo llegan a solaparse los enemigos… Como podriamos hacer para q esto no suceda? he intentando hacer obstaculos q se muevan por el teclado pero no lo he logrado.. alguna sugerencia? gracias

  2. Hola Carlos,

    La forma más rápida que se me ocurre es que compruebes si hay intersección entre cada uno de los objetos y su objeto más cercano con el movimiento que vas a aplicarle, si lo hay, no le aplicas el movimiento.

    Más sofisticado es el flocking (tengo una implementación en el blog si buscas por aquí). También tienes info al respecto aquí: http://www.red3d.com/cwr/boids/

Responder a anonymous Cancelar respuesta

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