Ir al contenido

publicidad
publicidad

Foto

Curso MM: 23 Ejemplo de inteligencia artificial


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

#1

Escrito 05 septiembre 2009 - 20:08

El siguiente ejemplo consiste en crear inteligencia artificial para un personaje de un juego usando una máquina de estados finitos. Tengo un ejemplo preparado en el que cogí el último ejemplo del libro (SpaceOut 4), le quité código de ese juego y añadí el código necesario para ver en funcionamiento la máquina de estados finitos.

Una máquina o autómata de estados finitos es un modelo de comportamiento compuesto por un número finito de estados y transiciones entre ellos.

El ejemplo que se utiliza en el programa contiene cuatro eventos y cuatro estados. Estando en cada uno de los estados sólo se puede producir un evento que nos lleve al siguiente estado (los demás eventos se ignoran en caso de producirse).

Si estamos en estado de vigilante y vemos al enemigo vamos a matarlo. Si le matamos buscamos botiquines para curar nuestras heridas. Cuando nos hayamos curado buscaremos munición para reponer las gastadas. Cuando tengamos la munición volvemos a vigilar.

Para compilar el ejemplo debéis añadir al proyecto los siguientes archivos: AlienSprite.cpp, Background.cpp, Bitmap.cpp, GameEngine.cpp, mef0001.cpp, mef0001.rc, Resource.h, Sprite.cpp y VigilanteSprite.cpp .

Los archivos que comentaré son: mef0001.cpp, AlienSprite.cpp y VigilanteSprite.cpp .



En mef0001.h hay que añadir sentencias #include para las clases AlienSprite y VigilanteSprite. También vemos en ese archivo que guardamos el evento y estado actual. Utilizo enum para llevar mejor el control en el código del estado y evento. Más abajo se ve la declaración de un vector de 4x4 para crear la máquina de estados finitos. En él se almacena por cada estado el estado al que pasamos al producirse cada evento. En la declaración de funciones al final hay cuatro funciones que se corresponden con los cuatro eventos: VerEnemigo, MatarEnemigo, EncontradaVida, EncontradaBalas.

[code:1]//defino los estados.
enum EstadoIA
{
VIGILANDO,
ATACANDO,
BUSCANDOVIDA,
BUSCANDOBALAS
};
//defino los eventos.
enum EventoIA
{
VERENEMIGO,
MATARENEMIGO,
ENCONTRADAVIDA,
ENCONTRADABALAS
};
//Variables globales. La finite state machine (fsm).
vector< vector > g_fsm(4, vector(4));

//-----------------------------------------------------------------
// Function Declarations
//-----------------------------------------------------------------
void NewGame();
void AddAlien();
void AddVigilante();
void UpdateHiScores();
BOOL ReadHiScores();
BOOL WriteHiScores();
void Inifsm(); //inicialización de la máquina de estados finitos.
void VerEnemigo();
void MatarEnemigo();
void EncontradaVida();
void EncontradaBalas();[/code]


En mef0001.cpp lo más interesante son las funciones siguientes:

[code:1]void Inifsm()
{
int estado;
int evento;

//rellenamos la máquina con valores por defecto.
for(estado=0;estado<4;estado++)
{
for(evento=0;evento<4;evento++)
{
//para cada estado por defecto llenamos con el
//mismo estado, así si sucede algún evento que
//no afecta a dicho estado no cambiamos a otro.
g_fsm[estado][evento] = estado;
}
}
//Ahora relleno los que si cambian:
g_fsm[VIGILANDO][VERENEMIGO] = ATACANDO;
g_fsm[ATACANDO][MATARENEMIGO] = BUSCANDOVIDA;
g_fsm[BUSCANDOVIDA][VERENEMIGO] = ATACANDO;
g_fsm[BUSCANDOVIDA][ENCONTRADAVIDA] = BUSCANDOBALAS;
g_fsm[BUSCANDOBALAS][VERENEMIGO] = ATACANDO;
g_fsm[BUSCANDOBALAS][ENCONTRADABALAS] = VIGILANDO;
}

//Funciones que responden a los eventos:
void VerEnemigo()
{
_iEstadoActual = g_fsm[_iEstadoActual][VERENEMIGO];
}

void MatarEnemigo()
{
_iEstadoActual = g_fsm[_iEstadoActual][MATARENEMIGO];
}

void EncontradaVida()
{
_iEstadoActual = g_fsm[_iEstadoActual][ENCONTRADAVIDA];
}

void EncontradaBalas()
{
_iEstadoActual = g_fsm[_iEstadoActual][ENCONTRADABALAS];
}[/code]

El resto de éste archivo es fácil de entender incluso aunque no hayáis seguido el curso.

Ahora vamos con AlienSprite.cpp. Está derivada de la clase Sprite. Y podemos tener el control de dos funciones: Update() y AddSprite(). En éste caso el comportamiento del alien es muy simple; no dispara, por lo que la segunda función no nos interesa y Update() lo único que hace es actualizar los datos de posición del alien.

VigilanteSprite.cpp: La clase Vigilante también se deriva de la clase Sprite. La función Update() se utiliza con la máquina de estados finitos para indicar el comportamiento del personaje, que dependerá de su estado. Es aquí donde se decide de forma aleatoria si disparamos al bicho. Si veis la sentencia switch, en el estado 1, es decir, ATACANDO , es donde está el código que indica al Sprite que utilice su función AddSprite() que sirve para crear un proyectil que se dirija hacia el alien.

Echad un vistazo, hay que definir todas las variables y funciones que usamos en el proyecto como "extern" si queremos usarlas desde aquí. En la sentencia switch utilizo un radio de visión de 150,150 alrededor del vigilante. Cuando ve a un extraterrestre extiendo el radio a 300,300. Lo perseguirá a tiros (a intervalos aleatorios) hasta que lo mate. Luego irá a las esquinas a por vida y munición y volverá al principio.

[code:1] switch(_iEstadoActual)
{
case 0:
this->m_pBitmap = _pEstado0Bitmap; //cambiamos la imagen por la del tipo vigilando.
if( abs(_iXVigilante-_iXAlien) < 150 && abs(_iYVigilante-_iYAlien) < 150 && _iNumAliens==1)
{
VerEnemigo(); //compruebo si el enemigo está en el radio de visión y paso al siguiente estado.
}
else
{
if(_iXVigilante>275 && _iXVigilante<325 && _iYVigilante>200 && _iYVigilante<250)
{
this->SetVelocity(0,0); //si estamos en la zona de vigilancia paro al vigilante.
if(_iNumAliens==2) _iNumAliens=0; // si no hay aliens (2), entonces cambio a 0
// y luego en mef0001.cpp si es cero cambia a 1 y crea un alien.
}
else
{
velocx = -2; //si no estamos en la zona de vigilancia entonces tenemos que ir hacia ella.
velocy = -2;
if(_iXVigilante<=300) velocx = 2;
if(_iYVigilante<=225) velocy = 2;
this->SetVelocity(velocx,velocy);
}
}
break;
case 1:
this->m_pBitmap = _pEstado1Bitmap; //ahora estamos atacando.
if(abs(_iXVigilante-_iXAlien) < 300 && abs(_iYVigilante-_iYAlien) < 300)
{
if(_iXVigilante<_iXAlien) //todo ésto es para moverse hacia el alien.
{
velocx = 1;
}
else
{
velocx = -1;
}
if(_iYVigilante<_iYAlien)
{
velocy = 1;
}
else
{
velocy = -1;
}
this->SetVelocity(velocx,velocy);
if ((rand() % (_iDifficulty / 2)) == 0)
{
_iDisparo = 1; //disparos aleatorios.
saSpriteAction |= SA_ADDSPRITE;
}
}
else
{
if(_iNumAliens==2) // si no hay aliens...
{
this->SetVelocity(0,0);
MatarEnemigo(); // ... pasamos al siguiente estado.
}
}
break;
case 2:
this->m_pBitmap = _pEstado2Bitmap; //buscamos botiquines.
if(_iXVigilante>500 && _iYVigilante>350)
{
EncontradaVida(); // si hemos llegado al botiquín vamos al siguiente estado.
}
else
{
velocx = -4; //en caso contrario vamos hacia el botiquín.
if(_iXVigilante<=550) velocx = 4;
velocy = -4;
if(_iYVigilante<=400) velocy = 4;
this->SetVelocity(velocx,velocy);
}
break;
case 3:
this->m_pBitmap = _pEstado3Bitmap; //éste es igual que el anterior.
if(_iXVigilante>500 && _iYVigilante<100)
{
EncontradaBalas();
}
else
{
velocx = -4;
if(_iXVigilante<=550) velocx = 4;
velocy = -4;
if(_iYVigilante<=50) velocy = 4;
this->SetVelocity(velocx,velocy);
}
break;
default:
break;
}[/code]

Y por último la función AddSprite() crea un disparo y lo dirige hacia el alien (de una forma muy rudimentaria, es una escopeta de fabricación casera :P ).

[code:1]Sprite* VigilanteSprite::AddSprite()
{
// Create a new missile sprite
RECT rcBounds = { 0, 0, 640, 480 };
RECT rcPos = GetPosition();
Sprite* pSprite = NULL;
int velocx,velocy;

//creo un sprite de bala del vigilante.
pSprite = new Sprite(_pVMissileBitmap, rcBounds, BA_DIE);
if(_iXVigilante<_iXAlien)
{
velocx = _iXAlien - _iXVigilante;
}
else
{
velocx = -1 * (_iXVigilante - _iXAlien);
}
if(_iYVigilante<_iYAlien)
{
velocy = _iYAlien - _iYVigilante;
}
else
{
velocy = -1 * (_iYVigilante - _iYAlien);
}
pSprite->SetVelocity(velocx*10/100, velocy*10/100);

// Set the missile sprite's position and return it
pSprite->SetPosition(rcPos.left + (GetWidth() / 2), rcPos.top + (GetHeight()/2));

return pSprite;
}[/code]

Código fuente


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