Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Enemy AI Movement

This thread is locked; no one can reply to it. rss feed Print
Enemy AI Movement
ObscurePaulie
Member #15,956
May 2015

Hi guys I was wondering if you could help me by explaining in simple terms as I'm new to Allegro coding, how I implement Finite State Machine AI into my game? Below is the coding I'm using but it's just not working. I know one problem is that the state isn't changing to IDLE during playing, so that is something that needs addressing. However, I also tried it without the states in and just used the chasing state code in my playing state and still nothing happens my enemies just fly across the screen regardless.

Any help on this would be greatly appreciated as it's the last part of my game that needs solving :)

#SelectExpand
1//global variables 2const int NUM_BULLETS = 5; 3const int NUM_ENEMIES = 5; 4const int NUM_EXPLOSIONS = 5; 5enum STATE{ TITLE, PLAYING, GAMEOVER, IDLE, CHASING, CONTINUE}; 6float CheckDistance(Player &player, Enemy enemy[], int size); 7float AngleToTarget(Enemy enemy[], int size, Player &player); 8 9Player player; 10Enemy enemy[NUM_ENEMIES]; 11 12//prototypes 13void InitPlayer(Player &player, ALLEGRO_BITMAP *image); 14void DrawPlayer(Player &player); 15 16void InitEnemy(Enemy enemy[], int size, ALLEGRO_BITMAP *image, ALLEGRO_SAMPLE *explode); 17void DrawEnemy(Enemy enemy[], int size); 18void StartEnemy(Enemy enemy[], int size); 19void UpdateEnemy(Enemy enemy[], int size); 20void CollideEnemy(Enemy enemy[], int size, Player &player, Explosion explosions[], int eSize); 21 22void ChangeState(int &state, int newState); 23 24int main() 25{ 26 bool done = false; 27 bool draw = true; 28 int FPS = 60; 29 int state = -1; 30 int threshold = 175; 31 enum KEYS { UP, LEFT, RIGHT, DOWN, SPACE }; 32 bool keys[5] = { false, false, false, false, false }; 33 34 if (!al_init()) 35 { 36 al_show_native_message_box(0, 0, "Error", "Falied to initialize allegro", 0, 0); 37 return -1; 38 } 39 40 display = al_create_display(ScreenWidth, ScreenHeight); 41 42 if (!display) 43 { 44 al_show_native_message_box(0, 0, "Error", "Falied to initialize the display", 0, 0); 45 return -1; 46 } 47 48 al_install_keyboard(); 49 al_init_primitives_addon(); 50 al_init_image_addon(); 51 al_init_font_addon(); 52 al_init_ttf_addon(); 53 al_install_audio(); 54 al_init_acodec_addon(); 55 56 playerImage = al_load_bitmap("Ship.png"); 57 enemyImage = al_load_bitmap("alien4.png"); 58 59 ChangeState(state, TITLE); 60 61 srand(time(NULL)); 62 InitPlayer(player, playerImage); 63 InitBullet(bullets, NUM_BULLETS, bulletImage, laser); 64 InitEnemy(enemy, NUM_ENEMIES, enemyImage, explode); 65 InitExplosions(explosions, NUM_EXPLOSIONS, explosion); 66 67 font18 = al_load_font("arial.ttf", 18, 0); 68 timer = al_create_timer(1.0 / FPS); 69 event_queue = al_create_event_queue(); 70 71 al_register_event_source(event_queue, al_get_timer_event_source(timer)); 72 al_register_event_source(event_queue, al_get_keyboard_event_source()); 73 al_register_event_source(event_queue, al_get_display_event_source(display)); 74 75 al_start_timer(timer); 76 77 while (!done) 78 { 79 ALLEGRO_EVENT ev; 80 al_wait_for_event(event_queue, &ev); 81 82 if (ev.type == ALLEGRO_EVENT_TIMER) 83 { 84 draw = true; 85 86 if (state == TITLE) 87 { 88 89 } 90 else if (state == PLAYING) 91 { 92 UpdateExplosions(explosions, NUM_EXPLOSIONS); 93 UpdateBullet(bullets, NUM_BULLETS); 94 StartEnemy(enemy, NUM_ENEMIES); 95 UpdateEnemy(enemy, NUM_ENEMIES); 96 CollideBullet(bullets, NUM_BULLETS, enemy, NUM_ENEMIES, player, explosions, NUM_EXPLOSIONS); 97 CollideEnemy(enemy, NUM_ENEMIES, player, explosions, NUM_EXPLOSIONS); 98 99 if (state == IDLE) 100 { 101 if (threshold > CheckDistance(player, enemy, NUM_ENEMIES)) 102 ChangeState(state, CHASING); 103 } 104 else if (state == CHASING) 105 { 106 if (threshold < CheckDistance(player, enemy, NUM_ENEMIES)) 107 ChangeState(state, CONTINUE); 108 else 109 { 110 float angle = AngleToTarget(enemy, NUM_ENEMIES, player); 111 enemy[NUM_ENEMIES].y += (2 * sin(angle)); 112 enemy[NUM_ENEMIES].x += (2 * cos(angle)); 113 } 114 } 115 else if (state == CONTINUE) 116 { 117 UpdateEnemy(enemy, NUM_ENEMIES); 118 } 119 if (player.lives <= 0) 120 ChangeState(state, GAMEOVER); 121 } 122 else if (state == GAMEOVER) 123 { 124 125 } 126 } 127 128 else if (ev.type == ALLEGRO_EVENT_KEY_DOWN) 129 { 130 switch (ev.keyboard.keycode) 131 { 132 case ALLEGRO_KEY_DOWN: 133 keys[DOWN] = true; 134 break; 135 case ALLEGRO_KEY_LEFT: 136 keys[LEFT] = true; 137 break; 138 case ALLEGRO_KEY_RIGHT: 139 keys[RIGHT] = true; 140 break; 141 case ALLEGRO_KEY_UP: 142 keys[UP] = true; 143 break; 144 case ALLEGRO_KEY_ESCAPE: 145 done = true; 146 break; 147 case ALLEGRO_KEY_SPACE: 148 keys[SPACE] = true; 149 if (state == TITLE) 150 ChangeState(state, PLAYING); 151 else if (state == PLAYING) 152 FireBullet(bullets, NUM_BULLETS, player); 153 else if (state == GAMEOVER) 154 ChangeState(state, PLAYING); 155 break; 156 } 157 } 158 else if (ev.type == ALLEGRO_EVENT_KEY_UP) 159 { 160 switch (ev.keyboard.keycode) 161 { 162 case ALLEGRO_KEY_DOWN: 163 keys[DOWN] = false; 164 break; 165 case ALLEGRO_KEY_LEFT: 166 keys[LEFT] = false; 167 break; 168 case ALLEGRO_KEY_RIGHT: 169 keys[RIGHT] = false; 170 break; 171 case ALLEGRO_KEY_UP: 172 keys[UP] = false; 173 break; 174 case ALLEGRO_KEY_SPACE: 175 keys[SPACE] = false; 176 break; 177 } 178 } 179 else if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) 180 { 181 done = true; 182 } 183 184 if (draw && al_is_event_queue_empty(event_queue)) 185 { 186 draw = false; 187 188 if (state == TITLE) 189 { 190 al_draw_bitmap(Title, 0, 0, 0); 191 } 192 else if (state == PLAYING) 193 { 194 al_draw_bitmap(background, 0, 0, 0); 195 DrawPlayer(player); 196 DrawBullet(bullets, NUM_BULLETS); 197 DrawEnemy(enemy, NUM_ENEMIES); 198 DrawExplosions(explosions, NUM_EXPLOSIONS); 199 200 al_draw_textf(font18, al_map_rgb(255, 0, 0), 5, 5, 0, "Lives: %i", player.lives); 201 al_draw_textf(font18, al_map_rgb(255, 0, 0), 5, 580, 0, "Score: %i", player.score); 202 } 203 else if (state == GAMEOVER) 204 { 205 al_draw_bitmap(GameOver, 0, 0, 0); 206 207 al_draw_textf(font18, al_map_rgb(255, 0, 0), ScreenWidth / 2, ScreenHeight / 2, ALLEGRO_ALIGN_CENTRE, "Final Score: %i", player.score); 208 } 209 al_flip_display(); 210 al_clear_to_color(al_map_rgb(0, 0, 0)); 211 } 212 } 213 214 al_destroy_display(display); 215 al_destroy_event_queue(event_queue); 216 al_destroy_timer(timer); 217 al_destroy_bitmap(playerImage); 218 al_destroy_bitmap(enemyImage); 219 220 return 0; 221 222} 223void InitPlayer(Player &player, ALLEGRO_BITMAP *image = NULL) 224{ 225 player.ID = PLAYER; 226 player.x = 55; 227 player.y = ScreenHeight / 2; 228 player.lives = 5; 229 player.speed = 6; 230 player.boundx = 25; 231 player.boundy = 25; 232 player.score = 0; 233 player.frameWidth = 50; 234 player.frameHeight = 50; 235 236 if (image != NULL) 237 player.image = image; 238} 239void DrawPlayer(Player &player) 240{ 241 al_draw_bitmap(player.image, player.x - player.frameWidth / 2, player.y - player.frameHeight / 2, 0); 242} 243void InitEnemy(Enemy enemy[], int size, ALLEGRO_BITMAP *image = NULL, ALLEGRO_SAMPLE *explode = NULL) 244{ 245 for (int i = 0; i < size; i++) 246 { 247 enemy[i].ID = ENEMY; 248 enemy[i].live = false; 249 enemy[i].speed = 4; 250 enemy[i].boundx = 15; 251 enemy[i].boundy = 15; 252 253 if (image != NULL) 254 enemy[i].image = image; 255 if (explode != NULL) 256 enemy[i].explode = explode; 257 } 258} 259void DrawEnemy(Enemy enemy[], int size) 260{ 261 for (int i = 0; i < size; i++) 262 { 263 if (enemy[i].live) 264 al_draw_bitmap(enemy[i].image, enemy[i].x - 10, enemy[i].y - 15, 0); 265 } 266} 267void StartEnemy(Enemy enemy[], int size) 268{ 269 for (int i = 0; i < size; i++) 270 { 271 if (!enemy[i].live) 272 { 273 if (rand() % 500 == 0) 274 { 275 enemy[i].live = true; 276 enemy[i].x = ScreenWidth; 277 enemy[i].y = 50 + rand() % (ScreenHeight - 100); 278 279 break; 280 } 281 } 282 } 283} 284void UpdateEnemy(Enemy enemy[], int size) 285{ 286 for (int i = 0; i < size; i++) 287 { 288 if (enemy[i].live) 289 { 290 enemy[i].x -= enemy[i].speed; 291 } 292 } 293} 294void CollideEnemy(Enemy enemy[], int size, Player &player, Explosion explosions[], int eSize) 295{ 296 for (int i = 0; i < size; i++) 297 { 298 if (enemy[i].live) 299 { 300 if (enemy[i].x - enemy[i].boundx < player.x + player.boundx && 301 enemy[i].x + enemy[i].boundx > player.x - player.boundx && 302 enemy[i].y - enemy[i].boundy < player.y + player.boundy && 303 enemy[i].y + enemy[i].boundy > player.y - player.boundy) 304 { 305 player.lives--; 306 enemy[i].live = false; 307 StartExplosions(explosions, eSize, player.x, player.y); 308 al_play_sample(enemy[i].explode, 0.5, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL); 309 } 310 else if (enemy[i].x < 0) 311 { 312 enemy[i].live = false; 313 } 314 } 315 } 316} 317float CheckDistance(Player &player, Enemy enemy[], int size) 318{ 319 for (int i = 0; i < size; i++) 320 { 321 if (enemy[i].live) 322 return sqrt(pow((float)player.x - enemy[i].x, 2) + pow((float)player.y - enemy[i].y, 2)); 323 } 324 325} 326float AngleToTarget(Enemy enemy[], int size, Player &player) 327{ 328 for (int i = 0; i < size; i++) 329 { 330 if (enemy[i].live) 331 { 332 float deltaX = (enemy[i].x - player.x); 333 float deltaY = (enemy[i].y - player.y); 334 return atan2(deltaX, deltaY); 335 } 336 } 337} 338 339void ChangeState(int &state, int newState) 340{ 341 if (state == PLAYING) 342 { 343 al_stop_sample_instance(SongInstance); 344 } 345 346 state = newState; 347 348 if (state == PLAYING) 349 { 350 InitPlayer(player); 351 InitBullet(bullets, NUM_BULLETS); 352 InitEnemy(enemy, NUM_ENEMIES); 353 InitExplosions(explosions, NUM_EXPLOSIONS); 354 355 al_play_sample_instance(SongInstance); 356 } 357 358}

taron 
Member #10,584
January 2009
avatar

      else if (state == PLAYING)
      {
        UpdateExplosions(explosions, NUM_EXPLOSIONS);
        UpdateBullet(bullets, NUM_BULLETS);
        StartEnemy(enemy, NUM_ENEMIES);
        UpdateEnemy(enemy, NUM_ENEMIES);
        CollideBullet(bullets, NUM_BULLETS, enemy, NUM_ENEMIES, player, explosions, NUM_EXPLOSIONS);
        CollideEnemy(enemy, NUM_ENEMIES, player, explosions, NUM_EXPLOSIONS);

        if (state == IDLE)
        {
          if (threshold > CheckDistance(player, enemy, NUM_ENEMIES))
            ChangeState(state, CHASING);
        }

It will never be able to enter the IDLE state.
Separate the game state and enemy state.

enum GameState { Title, Playing, GameOver };
enum EnemyState { Idle, Chasing, Continue };
...
GameState gameState;
...
enemy[i].state;

Aside from all that:

            float angle = AngleToTarget(enemy, NUM_ENEMIES, player);
            enemy[NUM_ENEMIES].y += (2 * sin(angle));
            enemy[NUM_ENEMIES].x += (2 * cos(angle));

This doesn't make much sense.
You seem to be trying to calculate the angle to a target but you pass a complete array and then you write outside of the bounds of the enemy array.

You probably want something like this.

for (int i = 0; i < NUM_ENEMIES; ++i)
{
    if (enemy[i].state == IDLE)
    ...
}

If you want to make an object follow another object in the most simple way, this would be faster than atan and cos/sin

float dx = target.x - object.x;
float dy = target.y - object.y;
float length = sqrt(dx * dx + dy * dy);
float x = dx / length * desiredLength;
float y = dy / length * desiredLength;
object.x += x;
object.y += y;

ObscurePaulie
Member #15,956
May 2015

I have made some of the changes suggested here, now the only thing I am unsure on is what is the desiredLength meant to be?

I don't have that as a variable anywhere in my code.

So what am I supposed to set it to?

I have this set up and all it is doing now is restarting my game every time an enemy should be following the player.

#SelectExpand
1 for (int i = 0; i < NUM_ENEMIES; i++) 2 { 3 if (enemy[i].state == IDLE) 4 { 5 if (threshold > CheckDistance(player, enemy, NUM_ENEMIES)) 6 ChangeState(state, CHASING); 7 } 8 else if (enemy[i].state == CHASING) 9 { 10 if (threshold < CheckDistance(player, enemy, NUM_ENEMIES)) 11 ChangeState(state, CONTINUE); 12 else 13 { 14 float desiredLength = 50; 15 float dx = player.x - enemy[i].x; 16 float dy = player.y - enemy[i].y; 17 float length = sqrt(dx * dx + dy *dy); 18 float x = dx / length * desiredLength; 19 float y = dy / length * desiredLength; 20 21 enemy[i].x += x; 22 enemy[i].y += y; 23 } 24 } 25 else if (enemy[i].state == CONTINUE) 26 { 27 UpdateEnemy(enemy, NUM_ENEMIES); 28 } 29 }

beoran
Member #12,636
March 2011

You are changing the game's state, not the enemy's state, and the check isn't correct.

if (enemy[i].state == IDLE)
{
  if (threshold > (player, enemy[i]))
  ChangeState(state, CHASING);
}

In stead check a bit more thoroughly and set the state of the enemmies correctly.

if (enemy[i].state == IDLE)
{
  if ((enemy[i].alive) && (threshold > CheckOneDistance(player, enemy[i]))) { 
    enemy[i].state = CHASING;
  }
}

...
float CheckOneDistance(Player &player, Enemy &enemy)
{
   return sqrt(pow((float)player.x - enemy.x, 2) 
             + pow((float)player.y - enemy.y, 2));
}

Also do this for the other item states. Keep at it, you're learning as you go! :)

taron 
Member #10,584
January 2009
avatar

desiredLength is the magnitude of the vector, or simply put how fast you want your object to move.

ObscurePaulie
Member #15,956
May 2015

Beoran, you sir are an absolute genius! I can not thank you enough!

I have it sort of working now with the help from yourself and Taron.

Only problem I have now is that my enemies seem to fly across the screen if they don't catch my player :P

and when I say fly I'm not meaning in a standard speed kinda way as they should be :P I mean literally too fast to attempt to shoot them :P

beoran
Member #12,636
March 2011

Probably the speed of the ennemies is set too high somehow. Try to reduce the speed.

taron 
Member #10,584
January 2009
avatar

With the FPS being 60 and the desiredLength being 50, they'd move at 3000 units/second and the units being pixels in this case that would be 3000 pixels per second.

ObscurePaulie
Member #15,956
May 2015

I know in my earlier code I had 50, that was just test numbers cos I was unsure of what to enter there. However, in my actual coding now I have the desiredLength set to 3 and my movements speed is only set to 4.

I commented out the code to make the enemies chase my player and they don't get super speedy, so it is something to do with this code here that is making my enemies go super speedy after so long...

#SelectExpand
1 for (int i = 0; i < NUM_ENEMIES; i++) 2 { 3 if (enemy[i].state == IDLE) 4 { 5 if ((enemy[i].live) && (threshold > CheckDistance(player, enemy[i]))) 6 enemy[i].state = CHASING; 7 } 8 else if (enemy[i].state == CHASING) 9 { 10 if ((enemy[i].live) && (threshold < CheckDistance(player, enemy[i]))) 11 enemy[i].state = CONTINUE; 12 else 13 { 14 float desiredLength = 1; 15 float dx = player.x - enemy[i].x; 16 float dy = player.y - enemy[i].y; 17 float length = sqrt(dx * dx + dy *dy); 18 float x = dx / length * desiredLength; 19 float y = dy / length * desiredLength; 20 21 enemy[i].x += x; 22 enemy[i].y += y; 23 24 if ((enemy[i].live) && (threshold < CheckDistance(player, enemy[i]))) 25 enemy[i].state = CONTINUE; 26 } 27 } 28 else if (enemy[i].state == CONTINUE) 29 { 30 UpdateEnemy(enemy, NUM_ENEMIES); 31 }

beoran
Member #12,636
March 2011

else if (enemy[i].state == CONTINUE)  {  UpdateEnemy(enemy, NUM_ENEMIES); }

This looks incorrect. Remember, you're looping over all enemies one by one, but UpdateEnemy updates many enemies at once. It doesn't make sense to do that in that particular place in your program. In stead, you should update the particular enemy[i] in the way you want it to be updated.

ObscurePaulie
Member #15,956
May 2015

Yeah I noticed that myself last night and forgot to update my post. I just decided to take it out of my coding and just use the 2 states for my enemies, as I have the enemies moving across screen in that updateenemies function in my game state of playing anyway. Thanks for the help though :)

Go to: