Ir al contenido

publicidad
publicidad

Foto

Curso MM: 17 Enseñando a los juegos a pensar


Este tema ha sido archivado. Esto significa que no puedes responder en este tema.
No hay respuestas en este tema

#1

Escrito 22 agosto 2009 - 17:44

Enseñando a los juegos a pensar

Entendiendo la inteligencia artificial

La IA son técnicas para emular el proceso de pensamiento humano en un ordenador.

Algunos sistemas tradicionales utilizan un sistema de algoritmos basados en información para tomar decisiones. En el pasado eran completamente deterministas, eso es que cada decisión podía ser revisada hacia atrás en un orden lógico predecible. Evidentemente los humanos no piensan así.

Los estudiosos de la inteligencia artificial se dieron cuenta y cambiaron a sistemas más realistas como los de “mejor conjetura”. En éstos tipos de IA se tienen en cuenta las experiencias pasadas, las tendencias personales o el estado de emoción actual, en conjunto con la toma lógica de decisiones.

Investigaciones más recientes tratan con la lógica difusa para llegar a decisiones de “mejor conjetura” en vez de las puramente lógicas. Otra área interesante son los algoritmos genéticos que tratan de conseguir pensamiento evolutivo. Los oponentes en éste caso podrían aprender según progresa el juego dando mayores y mejores desafíos al jugador.

Explorando los tipos de inteligencia artificial

En el libro se estudian tres tipos generales: errática, comportamiento y estratégica.

Errática: se refiere a movimientos de objetos, es decir, las decisiones que toman los objetos del juego que determinan cómo vagan alrededor del mundo virtual del juego. Se usa cada vez que un objeto debe tomar una decisión que alterará su rumbo. Suele ser muy fácil de implementar ya que sólo hay que cambiar la posición o la velocidad basándose en la posición de otro objeto. También se le pueden aplicar modelos aleatorios o predeterminados. Existen tres tipos: persecución, evasión y predeterminado.

- Persecución: un objeto observa y sigue a otro objeto o conjunto de ellos. El objeto que persigue basa su movimiento en la posición y velocidad del perseguido. Ejemplo:

[code:1]if (iXAlien > iXShip)
iXAlien--;
else if (iXAlien < iXShip)
iXAlien++;
if (iYAlien > iYShip)
iYAlien--;
else if (iYAlien < iYShip)
iYAlien++;[/code]


El único problema de éste código es que funciona demasiado bien. El perseguidor cazará obviamente al perseguido. Habrá que buscar que el perseguidor se demore o divague un poco, que su vuelo sea un poco imperfecto para dejar al perseguido la posibilidad de evitarlo. Queremos conseguir que el perseguidor tenga una tendencia a perseguir sin tener un 100% de probabilidad de conseguirlo.

[code:1]if ((rand() % 3) == 0) {
if (iXAlien > iXShip)
iXAlien--;
else if (iXAlien < iXShip)
iXAlien++;
}
if ((rand() % 3) == 0) {
if (iYAlien > iYShip)
iYAlien--;
else if (iYAlien < iYShip)
iYAlien++;
}[/code]


Aquí tenemos un sistema para dar al perseguidor una oportunidad entre tres de seguir al perseguido en cada dirección. Con eso damos al perseguido la posibilidad de escapar o luchar.

- Evasión: el objeto intenta escapar de otro objeto o conjunto de ellos. Se implementa de forma similar pero alterando las sumas y restas.

- Comportamiento predeterminado: se utiliza un juego de movimientos predeterminados. Normalmente se almacenan en tablas las velocidades y las posiciones o multiplicadores que se aplican cada vez que se necesita un movimiento de éste tipo.

[code:1]int iZigZag[2][2] = { {3, 2}, {-3, 2} };
iXAlien += iZigZag[iPatternStep][0];
iYAlien += iZigZag[iPatternStep][1];[/code]


Este ejemplo es de un movimiento predeterminado vertical en zig-zag. ipatternStep indica en qué paso estamos , es decir, si tomamos de la tabla el valor hacia la izquierda o hacia la derecha.

Comportamiento : éste sistema a veces utiliza una mezcla de varios sistemas erráticos. Es el que necesitamos si queremos que un objeto se comporte unas veces de una forma y otras de otra diferente, unas veces predeterminado, otras aleatorio. Un uso de éste sistema es aumentar la dificultad favoreciendo un comportamiento persecutorio antes que otro aleatorio o predefinido.

Para implementar éste sistema hay que crear estados para los objetos. Se pueden crear porcentajes para aplicar cada estado: 50% del tiempo se dedican a perseguir, 10% a evadir, 30% a seguir un movimiento predeterminado y 10% aleatorio. Otros objetos pueden tener otros porcentajes. El resultado es sorprendente porque nos da muchos comportamientos distintos. Un ejemplo:

[code:1]int iBehavior = abs(rand() % 100);
if (iBehavior < 50)
// chase
else if (iBehavior < 60)
// evade
else if (iBehavior < 90)
// fly in a pattern
else
// fly randomly[/code]


Estratégico: se usa para jugar a un juego con un sistema fijo de reglas bien definidas. Como ejemplo sirve cualquier juego de mesa. Normalmente requieren un sistema para ver los movimientos futuros y calcular el mejor. En éste sistema entran en juego los “pesos”. Se calculan movimientos y se les asigna peso; luego se utiliza el “menos malo” en vez de el mejor. Esto es así porque se trata de hacer el movimiento que menos ayude al oponente mejor que el que me lleva a ganar. Otras veces hay tantos cálculos que nos tenemos que conformar con el movimiento “suficientemente bueno”. Una de las mejores maneras de averiguar los movimientos suficientemente buenos es crear una partida con dos jugadores controlados por el ordenador y asignarles distintos algoritmos y pesos. Luego sólo queda sentarse a ver cómo evoluciona la partida y ver cuál es mejor. Así se consiguen jugadores ficticios muy buenos.

Desarrollando una estrategia de inteligencia artificial

Si lo que queremos es mantener al jugador entretenido y en constante desafío tendremos que utilizar la inteligencia artificial más simple posible. De hecho es mejor empezar con la más simple y luego ir mejorándola por fases. También hay que tener en cuenta el hecho de dejar una buena cantidad de tiempo para implementarla porque el 90% de las veces nos llevará más tiempo del que teníamos previsto llegar a un punto en el que estemos satisfechos.

El libro recomienda empezar con un diseño sobre papel y limitado a un sólo oponente. Empezar con un mapa pequeño y simple y reglas de movimiento sencillas. Escribir el código para llevar a un oponente del punto A al B. Luego añadir complicaciones pieza a pieza construyendo un algoritmo completo para cada paso. Si se hace con cuidado la inteligencia artificial será lo suficientemente general y abierta para conectar con otras piezas y manejar todas las condiciones que el juego requiera.

Construyendo el programa de ejemplo Roids 2
Roids.h , Roids.cpp

En la primera versión de éste juego había un campo de asteroides animado. Ahora le añadiremos un platillo volante con inteligencia suficiente para esquivarlos, o al menos intentarlo.

En el código de la cabecera se declara la función UpdateSaucer() que sirve para actualizar los datos del platillo. En el motor del juego ya se actualiza su posición y velocidad, pero aquí hace falta hacer más cambios en la velocidad que dependen de la proximidad de los asteroides. En Roids.cpp , en GameCycle() se llama a la función UpdateSaucer() que vemos a continuación:

[code:1]1: void UpdateSaucer()
2: {
3: // Obtener la posición del platillo.
4: RECT rcSaucer, rcRoid;
5: rcSaucer = _pSaucer->GetPosition();
6:
7: // Miramos cuál de los asteroides está más cercano al platillo.
8: int iXCollision = 500, iYCollision = 400, iXYCollision = 900;
9: for (int i = 0; i < 3; i++)
10: {
11: // Obtener la posición del asteroide.
12: rcRoid = _pAsteroids[i]->GetPosition();
13:
14: // Calcular la distancia mínima de colisión XY.
15: int iXCollisionDist = (rcSaucer.left +
16: (rcSaucer.right - rcSaucer.left) / 2) -
17: (rcRoid.left +
18: (rcRoid.right - rcRoid.left) / 2);
19: int iYCollisionDist = (rcSaucer.top +
20: (rcSaucer.bottom - rcSaucer.top) / 2) -
21: (rcRoid.top +
22: (rcRoid.bottom - rcRoid.top) / 2);
23: if ((abs(iXCollisionDist) < abs(iXCollision)) ||
24: (abs(iYCollisionDist) < abs(iYCollision)))
25: if ((abs(iXCollisionDist) + abs(iYCollisionDist)) < iXYCollision)
26: {
27: iXYCollision = abs(iXCollision) + abs(iYCollision);
28: iXCollision = iXCollisionDist;
29: iYCollision = iYCollisionDist;
30: }
31: }
32:
33: // Moverse para evitar el asteroide, si es necesario.
34: POINT ptVelocity;
35: ptVelocity = _pSaucer->GetVelocity();
36: if (abs(iXCollision) < 60)
37: {
38: // Ajustar la velocidad X.
39: if (iXCollision < 0)
40: ptVelocity.x = max(ptVelocity.x - 1, -8);
41: else
42: ptVelocity.x = min(ptVelocity.x + 1, 8);
43: }
44: if (abs(iYCollision) < 60)
45: {
46: // Ajustar la velocidad Y.
47: if (iYCollision < 0)
48: ptVelocity.y = max(ptVelocity.y - 1, -8);
49: else
50: ptVelocity.y = min(ptVelocity.y + 1, 8);
51: }
52:
53: // Actualizar el platillo a la nueva posición.
54: _pSaucer->SetVelocity(ptVelocity);
55: }[/code]


Lo que hace ésta función es buscar el asteroide más cercano al platillo y alterar la velocidad del platillo para que tenga tendencia a moverse en la dirección opuesta. Lo llamamos “tendencia” porque no lo hace de golpe, lo hace gradualmente. Eso da más realismo al movimiento del ovni. Para obtener el asteroide más cercano se busca la distancia de colisión mínima (la x,y) con los asteroides. Luego se suma la x y la y de la distancia de colisión para ver qué asteroide es el más cercano. Así evitamos que esté muy cerca en el eje x y muy lejos en el eje y.

Cuando terminamos con lo anterior tenemos dos datos importantes: la distancia de colisión en X y la distancia de colisión en Y. Ahora tenemos que comprobar si éstas distancias están bajo un mínimo que pone en peligro al platillo. En el ejemplo ese valor es 60 y se consigue con el método de ensayo-error. Comprobamos la distancia de colisión en x para ver si es menor de 60 y si lo es ajustamos la velocidad. Luego repetimos con y. Cuando acabamos ponemos la nueva velocidad con SetVelocity().

Código fuente


Este tema ha sido archivado. Esto significa que no puedes responder en este tema.
publicidad
publicidad