Comunidad de diseño web y desarrollo en internet online

Inteligencia Artificial parte 2

Citar            
MensajeEscrito el 03 Dic 2006 04:59 pm
Advertencia: como indica el título de este post, este tutorial está incompleto, faltando la parte primera. Antes de seguir leyendo recomiendo echarle un vistazo.

A quien vio la parte 1 y la siguió al pie de la letra, le diría que borre el final del código (el que ubiqué para mostrar lo que el código hacía) para dejar solo las funciones. Además, en la primera parte cometí 2 errores ya corregidos. La oveja se mueve 20% más lento, no rápido que los lobos cuando está tranquila. Además, en la función calcularDistancia, el Math.round estaba de más. Pequeños errores que saltaron a la vista al proseguir con el código.





Bien, lo que vamos a ver ahora es como programar la oveja para escapar (de los lobos nos ocuparemos más adelante, por su mayor complejidad y distinto enfoque).

Lo primero es aclarar que vamos a emplear prototipos y que un prototipo es, básicamente, una función que simula el código escrito en un símbolo.

Entonces, analicemos lo que queremos que haga nuestra oveja. Lo principal es huir de los lobos, pero el asunto es cómo. Como ya expuse en el post anterior, la oveja tendrá dos estados, determinados por una variable booleana (que es o bien true o false). En el primero escapará del lobo que tiene más cerca llendo en la dirección contraria y en el segundo calculará el ángulo de los dos lobos que tiene más cerca y escapará entre ellos; además, su velocidad variará dado que al estar asustada correrá más rápido.

Ok. Ahora, la sintaxis básica de un prototipo:

Código :

MovieClip.prototype.comportamientoOveja = function (parámetros) {
//
// Acciones
//
}
mi_clip.comportamientoOveja (parámetros);


En este caso, vamos a ubicar un onEnterFrame y un if dentro de este (aclaro desde ahora que la velocidad a la que configuré la película es de 30 fps), el primer parámetro será el estado inicial y el segundo el nombre del clip (que usaremos para llamar a las funciones) y usaremos un switch para setear la variable "tranquila", esto solo para simplificar la tarea de adaptar el código al rehusarlo

Código :

MovieClip.prototype.comportamientoOveja = function (estado:String, nombre:String) {
   //
   //
   switch (estado) {
   case "tranquila" :
      this.tranquila = true;
      break;
   case "asustada" :
      this.tranquila = false;
      break;
   default :
      trace ("Estado desconocido en el prototipo comportamientoOveja");
   }
   //    
   // 
   this.onEnterFrame = function () {
      //
      if (tranquila) {
      }
   };
};


¿Recuerdan la función calcularAngulo de la primera parte? ¿Y calcularDistancia? Bien, este es el momento de usarlas. Dentro del onEnterFrame ubicamos este código:

Código :

if (this.tranquila) {
//
this.angulo = calcularAngulo (nombre, buscarLobos (nombre, 2));
//
this._y -= _root.velocidad * Math.cos (this.angulo * (Math.PI / 180));
this._x += _root.velocidad * Math.sin (this.angulo * (Math.PI / 180));
}


Ahora, en la función crearObjetos de la parte 1, abajo del attachMovie con el que agregábamos la oveja, ubicamos este código que aplica nuestro prototipo y setea sus 2 parámetros:

Código :

_root["oveja" + i].comportamientoOveja ("tranquila", ("oveja" + i).toString ());


De todos modos, todavía no funciona, falta lo principal que es setear la variable velocidad. Esto lo hacemos en la capa variables de la que hablé en la parte 1.

Código :

var velocidad:Number = 1;


Ahora el otro estado, el que se activa cuando está asustada.

Bueno, es importante saber que cuando se asusta la oveja se comporta distinto. Lo que hace es calcular el ángulo intermedio entre los 2 lobos más cercanos. Entonces, además de la función buscarLobos, hay que usar un aque nos diga cuál es el segundo lobo. Bien, aquí está. Se puede observar que es casi idéntica a buscarLobos, pero que no puede tomar el valor que devuelve buscarLobos.

Código :

buscarSegundoLobo = function (oveja:String, numeroLobos:Number) {
   //
   var distMin = 1000;
   var lobo;
   var distancia2 = calcularDistancia (oveja, buscarLobos (oveja, numeroLobos));
   //
   for (i = 1; i <= numeroLobos; i++) {
      //
      var distancia = calcularDistancia (oveja, "lobo" + i);
      // 
      if (distancia < distMin && distancia > distancia2) {
         distMin = distancia;
         lobo = "lobo" + i;
      }
   }
   return lobo;
};


Entonces, hace falta una nueva función que calcule el ángulo en el que la oveja escapará cuando se asuste. A esta función la llamamos calcularAngulo2.



Código :

//
// Calcular el ángulo en el que se moverá la oveja para escapar de varios lobos
calcularAngulo2 = function (oveja:String, lobo1:String, lobo2:String) {
//
// Calculamos la distancia en X y Y entre la oveja y el lobo1
var deltaX = _root[oveja]._x - _root[lobo1]._x;
var deltaY = _root[oveja]._y - _root[lobo1]._y;
//
// Calculamos el ángulo en base a esas distancias
var angulo1 = (-Math.atan2 (deltaX, deltaY) / (Math.PI / 180)) + 180;
//
if (angulo1 < 0) {
angulo1 += 360;
}
//      
// Calculamos la distancia en X y Y entre la oveja y el lobo2
var deltaX = _root[oveja]._x - _root[lobo2]._x;
var deltaY = _root[oveja]._y - _root[lobo2]._y;
//
// Calculamos el ángulo2 en base a esas nuevas distancias
var angulo2 = (-Math.atan2 (deltaX, deltaY) / (Math.PI / 180)) + 180;
//
if (angulo2 < 0) {
angulo2 += 360;
}
//      
// Ahora, el ángulo final
var angulo = (Math.max (angulo1, angulo2) - Math.min (angulo1, angulo2)) / 2 + Math.min (angulo1, angulo2);
//
if (Math.max (angulo1, angulo2) - Math.min (angulo1, angulo2) > 180) {
angulo += 180;
}
//     
return angulo;
};



Aunque no lo parezca, esta función no es realmente complicada. Aunque quizá, con más conocimientos de trigonometría de los que dispongo (la verdad, no son muchos) se podría acortar. De una forma u otra, esta función es casi la misma que calcularAngulo o, al menos, tiene la misma lógica.


Pero si la prueban (más abajo, dejo el prototipo para hacerlo) verán que la oveja no parece tomar en cuenta la cercanía de los lobos. Ya que la considera (sí, supongamos que está pensando) igual. En otras palabras, escapa considerando que la distancia entre ella y cada uno de los lobos es idéntica.

Pero como no lo es, hay que cambiar algo en el programa.


Quiero que por un minuto imaginen la situación (así razonamos la respuesta). Bien, la oveja calcula el ángulo medio que se forma entre los dos ángulos de escape formados por cada lobo. Si un lobo se halla a 10 píxeles de la oveja y el otro a 300, el lobo que se halla más cerca va a tener buenas oportunidades de atraparla. Esto es porque la oveja no escapa en la dirección opuesta a la del lobo.


En este momento sería útil que analizaran cómo solucionar el problema. Por si acaso, les dejo el prototipo y las acciones, para que puedan hacerlo apropiadamente:


PROTOTIPO:

Código :

MovieClip.prototype.comportamientoOveja = function (estado:String, nombre:String) {
//
//
switch (estado) {
case "tranquila" :
this.tranquila = true;
break;
case "asustada" :
this.tranquila = false;
break;
default :
trace ("Estado desconocido en el prototipo comportamientoOveja");
}
//    
// 
this.onEnterFrame = function () {
//
if (!this.tranquila) {
//
this.angulo = calcularAngulo (nombre, buscarLobos (nombre, 2));
}
if (this.tranquila) {
//
this.angulo = calcularAngulo2 (nombre, buscarLobos (nombre, 2), buscarSegundoLobo (nombre, 2));
}
this._y -= _root.velocidad * Math.cos (this.angulo * (Math.PI / 180));
this._x += _root.velocidad * Math.sin (this.angulo * (Math.PI / 180));
};
};



ACCIONES:

Código :

//
crearObjetos (1, 2);
//
oveja1._y = 200;
oveja1._x = 275;
//
lobo1._x = 540;
lobo1._y = 10;
lobo2._y = 290;
lobo2._x = 300;
//
lobo2._xscale = 180;
//
onMouseMove = function () {
lobo1._x = _xmouse;
lobo1._y = _ymouse;
};





Entonces, si lo prueban, verán que el lobo controlado por el mouse tendría altas probabilidades de atrapar a la oveja si no escapa en otro ángulo.

Sería un buen ejercicio que se detuvieran a pensar las distintas soluciones que existen para solucionar el problema.




Lo hicieron? Bien, prosigamos (si, ya sé que muchos no le dedicaron tiempo, pero de haberlo hecho ya tendrían la solución y la satisfacción de haberla encontrado).


A mí se me ocurrieron dos modos relativamente simples. El primero sería hacer un sub-estado del estado "asustada" que se active sólo cuando la oveja tiene al primer lobo muy cerca y al segundo lejos. La otra solución es que luego de calculado el ángulo de escape entre 2 lobos, sume o reste un par de números a este ángulo para acomodarlo al nuevo peligro.

Particularmente, me gusta la segunda idea ya que es más simple y consigue el mismo efecto.


Lo primero que hay que hacer es comparar la distancia entre la oveja y cada uno de los lobos.

Esta función se encarga de hacerlo:

Código :

calcularPeligro = function (oveja:String, lobo1:String, lobo2:String) {
//
var peligro = false;
//
var dist1 = calcularDistancia (oveja, lobo1);
var dist2 = calcularDistancia (oveja, lobo2);
//  
var distMin = Math.min (dist1, dist2);
var distMax = Math.max (dist1, dist2);
//
var difDist = distMax / distMin;
//
// Aquí van las constantes que definen si la oveja corre o no peligro
// En caso de que algono funcione suficientemente bien, se cambian los números
if (distMin < 65) {
peligro = true;
} else if (difDist > 4 && distMin < 100) {
peligro = true;
}
//             
return peligro;
};


(como todas las otras funciones que armé hasta ahora, sacrifica brevedad por legibilidad, si la hubiese abreviado, simplemente sería más difícil de leer y, tomando en cuenta que este es un tutorial, eso no es bueno U_U )


Ahora, la pregunta clave, cómo la utilizamos?


Bien, a calcularAngulo2 le agregamos estas líneas:

Código :

//   
// El ángulo final puede cambiar si la oveja se halla en peligro inmediato
// En ese caso, la oveja calcula una nueva ruta de escape
if (calcularPeligro (oveja, lobo1, lobo2)) {
//
// AQUÍ VA EL CÓDIGO QUE CAMBIA EL ÁNGULO
//
}


Bien, parece simple, esto lo agregamos entre el return y la llave que cierra el último if.


Ahora, el código para variar el ángulo, se puede observar que casi no escribimos nada nuevo, sino que reusamos partes de código ya escritas:

Código :

var nuevoAngulo = calcularAngulo (oveja, buscarLobos (oveja, 2));
var anguloMedio = (Math.max (angulo, nuevoAngulo) - Math.min (angulo, nuevoAngulo)) / 2 + Math.min (angulo, nuevoAngulo);
//
if (Math.max (angulo, nuevoAngulo) - Math.min (angulo, nuevoAngulo) > 180) {
anguloMedio += 180;
}
//   
return anguloMedio;


Claro, esto va reemplazando el comentario del pedazo de código de arriba.



Para cerrar, el código del prototipo que incluye el código por el cuál se asusta la oveja (se acuerdan??)

Código :

if (buscarLobos (nombre, 2) != this.lobo) {
this.miedo++;
}
if (this.miedo > this.tolerancia) {
this.tranquila = false;
trace ("Oveja asustada");
}
//     
this.lobo = buscarLobos (nombre, 2);


Ese código, necesita las variables this.miedo y this.tolerancia. Luego de definirlas debajo del switch, el código nos queda así:

Código :

MovieClip.prototype.comportamientoOveja = function (estado:String, nombre:String) {
//
//
switch (estado) {
case "tranquila" :
this.tranquila = true;
break;
case "asustada" :
this.tranquila = false;
break;
default :
trace ("Estado desconocido en el prototipo comportamientoOveja");
}
//
this.tolerancia = 10;
this.miedo = 0;
//    
// 
this.onEnterFrame = function () {
//
if (this.tranquila) {
//
this.angulo = calcularAngulo (nombre, buscarLobos (nombre, 2));
//
if (buscarLobos (nombre, 2) != this.lobo) {
this.miedo++;
}
if (this.miedo > this.tolerancia) {
this.tranquila = false;
trace ("Oveja asustada");
}
//     
this.lobo = buscarLobos (nombre, 2);
}
if (!this.tranquila) {
//
this.angulo = calcularAngulo2 (nombre, buscarLobos (nombre, 2), buscarSegundoLobo (nombre, 2));
}
this._y -= _root.velocidad * Math.cos (this.angulo * (Math.PI / 180));
this._x += _root.velocidad * Math.sin (this.angulo * (Math.PI / 180));
};
};


Claro, el trace ("oveja asustada") sólo está ahí para mostrar cuándo exactamente se asusta la oveja. No tiene ningún uso y hasta resulta molesto. Luego de ver que funciona, la idea es sacarlo. En mi opinión es una buena costumbre poner trace en ciertas variables para ver que es lo que está fallando o cómo funcionan ciertas cosas. Ya que hacer un debug es algo engorroso.




Para cerrar esta "entrega", voy a aclarar lo que acabamos de hacer. Básicamente, aplicamos la lógica (y un poco de matemática) para resolver una serie de problemas que se fueron presentando.

Luego de pensarlo seriamente, opté por incluir paso a paso el código que usé, no para entregar el programa ya hecho, sino para que quien lea esta explicación, sea capaz de razonarlo del mismo modo que lo hice. La idea, sería detenerse en cada paso y probar el código. Incluso, me alegraría mucho recibir feedback que me ayude a mejorarlo.

Reitero, la idea no es copiar todo el código y simplemente adaptarlo, sino usarlo como guía para ver cómo se pueden solucionar ciertos problemas relativos a la creación de personajes controlados por computadora.




Los objetivos puntuales fueron los siguientes:

1) Mostrar una forma simple de cambiar estados en la oveja, elemento importante en la creación de un videojuego relativamente complejo. Una variación de esto sería setear la dificultad del juego.

2) Ejemplificar el código reutilizable o adaptable, lo que simplifica no sólo el escribir el código, sino también pensar en usarlo en otra aplicación. Un buen ejemplo de esto es la función calcularDistancia, que estoy seguro tiene muchos más usos de los que expuse.

3) Mostrar que uno de los acercamientos más acertados para crear IA es armarla modularmente, con varias funciones que se puedan emplear en distintos momentos y con distintos motivos. Retomando el ejemplo de calcularDistancia, esa función se puede usar para la oveja o los lobos (esto lo trataré más adelante).





Quizá algún lector se halla dado cuenta que el agregado a calcularAngulo2 es inútil (ese que modifica el ángulo con la función calcularPeligro), porque dado el modo en el que la oveja se asusta, logra tener a los 2 lobos prácticamente a la misma distancia. Le recuerdo al avispado lector que la oveja podría estar ya asustada "de nacimiento" y que no necesariamente vamos a trabajar con sólo 2 lobos :wink:





Realmente, espero que esto sirva y cualquier pregunta, estoy ahí para responderla. Nos vemos en la última entrega.


EDITADO: Corregí todas las etiquetas code que estaban mal puestas :wtf:

Por HernanRivas

Claber

3416 de clabLevel

26 tutoriales

 



Ultima edición por HernanRivas el 04 Dic 2006 01:07 pm, editado 1 vez

Argentina

msie
Citar            
MensajeEscrito el 04 Dic 2006 12:58 pm
Intersante código... a ver si cuando termines la siguiente entrega le metes un ejemplo en .FLA y lo recopilas todo en un tuto (y)

PD. Has colocado todas las [code] mal... :roll:

Por Zguillez

BOFH

10744 de clabLevel

85 tutoriales
17 articulos
3 ejemplos

Genero:Masculino   Bastard Operators From Hell Héroes Team Cristalab Editores

BCN

opera
Citar            
MensajeEscrito el 04 Dic 2006 01:00 pm

Zguillez escribió:

PD. Has colocado todas las [code] mal... :roll:


Si, es verdad, pasa que lo escribí en el notepad. Qué es lo que hice mal?

Por HernanRivas

Claber

3416 de clabLevel

26 tutoriales

 

Argentina

msie
Citar            
MensajeEscrito el 04 Dic 2006 01:02 pm

HernanRivas escribió:

Si, es verdad, pasa que lo escribí en el notepad. Qué es lo que hice mal?

Pusiste mal la barra al cerrar la etiqueta [\code] --> [/code] :lol:

Por Zguillez

BOFH

10744 de clabLevel

85 tutoriales
17 articulos
3 ejemplos

Genero:Masculino   Bastard Operators From Hell Héroes Team Cristalab Editores



Ultima edición por Zguillez el 04 Dic 2006 01:03 pm, editado 1 vez

BCN

opera
Citar            
MensajeEscrito el 04 Dic 2006 01:03 pm
Ahhh :lol:

Ahora lo edito :wink:

Por HernanRivas

Claber

3416 de clabLevel

26 tutoriales

 

Argentina

msie
Citar            
MensajeEscrito el 15 Ene 2007 09:45 pm
Muy bueno , ahaha pero pudiaras en la proxima poner un ejemplo (ya sabes da pereza estar copiando y pegando el codigo) solo para hechar un vistazo que te impacte

Por eveevans

Claber

450 de clabLevel

3 tutoriales

 

Nicaragua

firefox
Citar            
MensajeEscrito el 15 May 2012 03:53 am
muy bueno !; no tien un swf para que pongas al final para ver como funcionna ?

Por hax_1000

Claber

211 de clabLevel



Genero:Masculino  

Actionscript, PHP.

opera
Citar            
MensajeEscrito el 09 Abr 2013 12:39 am
Seria bastante interesante ver el archivo .FLA
o incluso un video tutorial
Esta muy bueno.

Por Care_Susto

8 de clabLevel



 

chrome

 

Cristalab BabyBlue v4 + V4 © 2011 Cristalab
Powered by ClabEngines v4, HTML5, love and ponies.