Inteligencia Artificial con AS3. (Seguimiento y persecución/Aiming-Chasing AI)

Al finalizar el tutorial, serás capaz de implementar Inteligencia Artificial básica con AS3 para encontrar un objetivo en un escenario. Este concepto es ampliamente aplicado en el campo de los videojuegos y es conocido como aiming y chasing.

En este tutorial aprenderas a programar...

Aprenderas a programar el comportamiento de un enemigo que busca su objetivo. En este tutorial, tenemos una imagen al centro que simula una fuente de luz, y una sombra que simula ser un hombre. La fuente de luz girara en torno a su propio eje para quedar cara a cara con su objetivo, en el minimo tiempo posible. El resultado final se muestra a continuacion:

Los archivos

Los archivos del tutorial, los puedes descargar de este enlace.
Inteligencia Articial con AS3

Requisitos

  • Flash CS3 o superior
  • Conocimientos basicos de programacion orientada a objetos
  • Trigonometria elemental.

Preparando el ambiente de trabajo

  1. Crea dos archivos diferentes buscarObjetivo.fla y aiming.as. Si tienes dudas de como crearlos, visita el post clases externas as3 en flash. (haz click en el enlace)
  2. Exporta dos clips de pelicula a ActionScript con el nombre de clase Enemigo y Jugador. Sigue el enlace para aprender como exportar clips de pelicula a ActionScript 3.
    Las imagenes que utilicé para este tutorial fueron las siguientes (Enemigo y Jugador respectivamente)
    Imagen del Enemigo Imagen del Jugador

El código

Copia y pega el siguiente código en el entorno de programación de Flash y compilalo. Si seguiste el tutorial al pie de la letra, no debes tener ningun problema al ejecutarlo.

package 
{
	import flash.display.*;
	import flash.utils.*;
	import flash.events.*;
	import flash.geom.*;
	public class aiming extends MovieClip
	{
		static const ESTADO_INICIAR:int = 10;
		static const ESTADO_JUGAR:int = 20;
		static var estadoDeJuego:int = 0;
		static const enemigoVelocidadDeGiro:Number = 0.25;
		static const enemigoVelocidad:Point = new Point(0,0);
		static var enemigoAngulo:Number = 0.0;
		static var enemigoPosicion:Point = new Point(275,200);
		static var jugadorPosicion:Point;
		static var enemigo:MovieClip;
		static var jugador:MovieClip;
		static var enemigoRotacionEnRadianes:Number;
		public function aiming()
		{
			enemigo = new Enemigo  ;
			jugador = new Jugador  ;
			enemigo.x = enemigoPosicion.x;
			enemigo.y = enemigoPosicion.y;
			enemigo.rotation = 0;
			addEventListener(Event.ENTER_FRAME,cicloDelJuego);
			estadoDeJuego = ESTADO_INICIAR;
		}
		public function cicloDelJuego(e:Event):void
		{
			switch (estadoDeJuego)
			{
				case ESTADO_INICIAR :
					inicializarIA();
					break;
				case ESTADO_JUGAR :
					iniciaIA();
					break;
			}
		}
		public function inicializarIA():void
		{
			addChild(enemigo);
			addChild(jugador);
			jugador.startDrag(true,new Rectangle(0,0,550 - jugador.width,400 - jugador.height));
			estadoDeJuego = ESTADO_JUGAR;
		}
		public function iniciaIA():void
		{
			jugadorPosicion = new Point(jugador.x,jugador.y);
			enemigoPosicion = new Point(enemigo.x,enemigo.y);
			enemigoRotacionEnRadianes = enemigo.rotation * Math.PI / 180;
			enemigoVelocidad.x = Math.cos(enemigoRotacionEnRadianes);
			enemigoVelocidad.y = Math.sin(enemigoRotacionEnRadianes);
			enemigo.x +=  enemigoVelocidad.x;
			enemigo.y +=  enemigoVelocidad.y;
			enemigo.rotation = ((buscarEntidad(enemigoPosicion,jugadorPosicion,enemigoRotacionEnRadianes,enemigoVelocidadDeGiro) * 180) / Math.PI);
			seSalio();
		}
		public function buscarEntidad(posicion:Point,buscaEsto:Point,anguloActual:Number,velocidadDeGiro:Number):Number
		{
			var puntoLocal:Point = buscaEsto.subtract(posicion);
			var anguloDeseado:Number = Math.atan2(puntoLocal.y,puntoLocal.x);
			var diferencia:Number = restringirAngulo((anguloDeseado - anguloActual));
			diferencia = restringirNumero(diferencia, -  velocidadDeGiro,velocidadDeGiro);
			return restringirAngulo((anguloActual + diferencia));
		}
		public function seSalio():void
		{
			if (enemigo.x < 0 || enemigo.x > 400)
			{
				enemigo.rotation = ((buscarEntidad(enemigoPosicion,jugadorPosicion,enemigoRotacionEnRadianes,enemigoVelocidadDeGiro) * 180) / Math.PI);
			}
			if (enemigo.y < 0 || enemigo.y > 550)
			{
				enemigo.rotation = ((buscarEntidad(enemigoPosicion,jugadorPosicion,enemigoRotacionEnRadianes,enemigoVelocidadDeGiro) * 180) / Math.PI);
			}
		}
		public function restringirNumero(numero:Number,bajo:Number,alto:Number):Number
		{
			return Math.max(bajo,Math.min(numero,alto));
		}
		public function restringirAngulo(radianes:Number):Number
		{
			while ((radianes <  -  Math.PI))
			{
				radianes +=  Math.PI * 2;
			}
			while ((radianes > Math.PI))
			{
				radianes -=  Math.PI * 2;
			}
			return radianes;
		}
	}
}

Variables, constantes y funciones. La ciencia detrás del código.

Es en esta parte donde echaremos a andar todos nuestros conocimientos de trigonometría a andar, ya que encontraremos el numero de grados que nuestro enemigo debe girar, para poder quedar cara a cara con su objetivo y además, hacerlo en el menor tiempo posible para que no escape!

Estados:

Una vez mas implementamos la maquina de estados que habiamos programado en el tutorial de como programar un videjuego con AS3.

  • ESTADO_INICIAR, ESTADO_JUGAR. Son las variables de la máquina de estados que esta constantemente monitoreando el comportamiento del videojuego, dependiendo del estado en el que se encuentre el juego, se ejecutará una función. La máquina de estados y sus comportamientos son explicados a detalles en el apartado de Las funciones

Constantes:

  • enemigoVelocidadDeGiro. Es una constante definida en radianes/pixel. Basicamente controla que tan rapido gira el enemigo para ponerse cara a cara. El rango de valores ideales es de 0 a 1. Numeros superiores a 0.5 tienen tiempos de reaccion casi inmediato. Ajustado a 0.25 por default.

Variables globales:

  • estadoDeJuego. Es la variable que se encarga de almacenar el estado actual del videojuego.
  • enemigoVelocidad. Es la velocidad a la que se mueve el enemigo cuando esta persiguiendo a su objetivo. Es una variable tipo Point donde definimos sus componentes X y Y a cero respectivamente.
  • enemigoPosicion. La posicion en el escenario del enemigo.
  • enemigo. Es la instancia del objeto MovieClip que contiene la imagen del enemigo.
  • enemigoRotacionEnRadianes. Es la variable que contiene el angulo de rotacion del enemigo en radianes.
  • jugador. Es la instancia de la clase MovieClip que contiene la imagen del jugador
  • jugadorPosicion. Es la variable que contiene la posicion del jugador en el escenario

Variables locales:

  • puntoLocal. Variable tipo Point que contiene el valor de la distancia del enemigo al jugador
    .
  • anguloActual. Contiene el valor del angulo actual del enemigo. (hacia donde se encuentra mirando).
  • anguloDeseado. Es el angulo que el enemigo debe buscar, para ir a perseguir al jugador.
  • diferencia. Contiene el valor de la diferencia de angulos que existe entre el anguloDeseado y el anguloActual
  • posicion. Contiene la posicion del enemigo en el escenario
  • buscaEsto. Contiene la posicion del objeto a buscar en el
    escenario.
  • velocidadDeGiro. Es la variable que se encarga de restringir la velocidad de giro del enemigo.
  • radianes. Es la variable que se encarga de restringir el giro del enemigo.
  • numero. Parametro de la funcion restringirNumero()
  • bajo. Parametro de la funcion restringirNumero()
  • alto. Parametro de la funcion restringirNumero()

Las funciones:

  • aiming

    . Es el constructor de la clase, se encarga de inicializar e instanciar los objetos del juego.

  • cicloDelJuego

    . En este método, creamos nuestra máquina de estados. Es la función que se va a encargar de monitorear el estado actual del videojuego.

  • inicializarIA

    . Cuando el estadoDeJuego toma el valor ESTADO_INICIAR, esta funcion se encarga de inicializar la inteligencia artificial del juego. Dibuja las imagenes en pantalla, e inicia el movimiento del jugador. Cambia la variable de estado a ESTADO_JUGAR, que inicia la inteligencia artificial del juego.

  • iniciaIA

    . Monitorea la posicion, rotacion y movimiento del enemigo, asi como la posicion del jugador. El movimiento del enemigo esta descompuesto en sus componentes X,Y y calculado mediante trigonometria basica. La rotacion del jugador, en grados, se encuentra llamando a la funcion buscarEntidad.
    NOTA: El parametro rotation de la clase MovieClip almacena valores en GRADOS no en radianes.

  • buscarEntidad

    como calcular la posicion de un objeto en el escenario
    Explicación de la función buscarEntidad()

    . Aqui es donde se encuentra la inteligencia artificial de nuestro juego. Toma como argumento la posicion del enemigo, la posicion del jugador, El angulo de rotacion del enemigo en RADIANES y la velocidad de giro del enemigo.
    El calculo de los grados, y la distancia se hace conforme a la siguiente grafica:

    La funcion tiene que encontrar el angulo theta (anguloDeseado), que debe tener el enemigo para que se encuentre mirando cara a cara a su objetivo.
    Para lograrlo, encuentra la distancia en X y Y con respecto al objetivo.
    Vemos en la imagen, dibujado con verde, que se forma un triangulo rectangulo, y el angulo que se forma entre las componentes se encuentra utilizando la funcion trigonometrica tangente

    Utilizaremos la funcion atan2 (arco tangente) que tiene la funcion agregada de que utilizara los signos de la diferencia de las componentes en X y Y, para saber el cuadrante en el que se encontrara el angulo.
    De esa manera sabemos, hacia que direccion queremos que nuestro enemigo observe.

    Ahora necesitamos calcular que tanto necesitamos que nuestro enemigo gire, por lo tanto llamamos a la funcion restringirAngulo, que mantendra nuestro giro en el rango de -180 a 180 grados (-PI a PI)
    Ese valor, lo almacenamos en la variable "diferencia", la cual ahora la vamos a mantener a una velocidad "constante", ahora la vamos a pasar por la funcion restringirNumero, para que no exceda la velocidad maxima permitida del giro del enemigo.

    Y finalmente encontramos que lo mas cerca que estaremos de nuestro objetivo es la suma del anguloActual + diferencia, y regresamos ese valor a la funcion, restringiendolo en el rango de -180 a 180 nuevamente.

  • restringirNumero

    ActionScript 3 no implementa de manera nativa el metodo Clamp, que cualquier libreria matemática debe tener. Asi que lo implementamos, en esta funcion. El objetivo de Clamp es mantener un valor dentro de un rango de numeros, o en su defecto regresar el valor ingresar.
    numero < bajo, entonces regresa bajo. numero > alto, entonces regresa alto.
    Cualquier otra situacion, regresa numero.

  • Basicamente restringe el angulo a un rango de -180 a 180 grados. Si el angulo es menor a -180 grados, agrega grados hasta que sea mayor o igual a -180, pero menor o igual a 180 grados. La funcion en ActionScript hace los calculos en radianes.
  • seSalio

    Monitorea si el enemigo se salio de la pantalla. Si se salio de la pantalla, entonces llama nuevamente a buscarEntidad para buscar a su objetivo.

  • Fletcher

    Muy buen tutorial con una explicación facil de entender. Buen trabajo!!!!

  • Ivan

    ¡Eres un genio! ¿Dónde habías estado?

  • Shoor

    Buenisimo el tuto!!
    gracias Man, Saludos!

    • Alan Chavez

      De nada, saludos!

  • Miguel

    Buenos días,

    Enhorabuena, muy bueno el tutorial!!! pero tengo una duda , ¿Cómo se podría hacer para que cuando el enemigo se encuentre a una distancia mínima, por ejemplo de un píxel , no siga avanzando ni rotando

    Saludos!!

    • Alan Chavez

      Primero tendrias que definir bajo que condicion quieres que se detenga el enemigo.
      digamos que cuando la posicion x,y del enemigo sea igual a la posicion x,y de tu personaje, entonces el enemigo debe detenerse, en ese caso:

      if(new Point(enemigo.x,enemigo.y) == new Point(personaje.x,personaje.y)) {
      enemigo.velocidad = 0;
      }

      No recuerdo exactamente el nombre de las variables que utilice en el ejemplo, pero esa seria mas o menos la logica :)

  • candido

    saludos:
    Estoy empezando en Flash y AS3.0, despues de pagar un curso aqui en españa 550euros de media ningumprofesor me pudo ayuda a:
    tenemos tres circulos graduados en 360 , A B y C.
    imagina que el A es fijo, el circuo B en interior de A y el grado 0den B, me señala donde apunta con respecto al A ( al girandolo consigo me diga apuntas a 360, a 40 etc) el problema es con el C que deseo me diga hacia donde apunta su 0 con respecto al B y sea dinamico, que al girar el B se actualice la lectura del C( puede un momento dado por ejemplo el A es fijo no gira, el B apunta hacia A y giaro lee 40 grado y el C al girarlo independiente este apuntando al B en 255grados

    Agradeceria ayuda

  • Excelente tutorial!

A %d blogueros les gusta esto: