
Keep it simple.
It’s a good motto to remember in life, in your ventures, and especially when you set out to make your own game. I can tell you with every game that I make I try to create some elaborate ideas. I think they are some amazing ideas (and maybe they truly are), but I sit down and begin coding and reality sets in. More complexity leads to more time. More time leads to more bugs. More bugs leads to the dark side.
To be honest for a moment, I imagine we all do that with many things in our life. Simpler isn’t always better, but I think being able to complete a task or a goal is more important.
My advice when designing your games and your game’s AI, stick to simple. If you can complete the simple idea first, then maybe later you can add those extra guns you wanted. Or maybe the cooler battle system. Or even more complex enemy attacks.
In the last tutorial, we created the first type of enemies for our spaceship, asteroids, and got them moving. This time, we will continue creating enemies for our player and think about how we can add some difficulty to our game.
The goal of today’s tutorial is to think of more types of enemy AI for our game, create them in Pygame, and then create different types of enemy attacks.
This is Part 2 of creating enemy AI for our game. If you are interested, please check out Part 1 so you can better follow this tutorial.
Here is the game I set out to make: I wanted to make a space shooter where the player is traveling through an asteroid field. But I felt just dodging and blowing up asteroids wasn’t enough. What if our player was also dodging enemy ships and taking on enemy fire? Sounds like quite a lot and I brooded over many different types of attacks: ones where multiple enemies attack in an ordered line, ones where they attack from left or right at random, or synchronous attacks from both sides, on and on.
Finally, I settled upon something more simplified. We already have asteroids hurling at our ship at random. Why not create enemies that appear somewhat at random, and fire two shots at our enemy, then as a last attempt, hurl themselves towards our player in kamikaze fashion.
I tried it out and found it added difficulty, but didn’t cause the game to be unplayable. So let’s get started.
THE CODE FOR THIS GAME CAN BE FOUND ON GITHUB.
Create Enemy Ships
Open up the space_shooter.py file and let’s begin by loading our spacecraft_enemy.png image.
1 2 |
# load enemy images enemy_img = pygame.image.load('images/spacecraft_enemy.png').convert_alpha() |
Locate the def main( ) function and add this bit of code.
Next we need to create a class for our enemy ships. The next part is a bit long, but it isn’t too difficult to understand after building the Player and Asteroid classes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class EnemyShip(pygame.sprite.Sprite): '''create EnemyShip class''' def __init__(self, enemy_image, bullet_image, sprites_list, bullet_list): super().__init__() # scale enemy image self.image = pygame.transform.scale(enemy_image, (60, 60)) self.rect = self.image.get_rect() # sprites list self.sprites = sprites_list # enemy starting location self.rect.centerx = random.randrange(90, WINDOWWIDTH - 90) self.rect.bottom = random.randrange(-150, -20) # bullet attributes for enemy self.bullet_image = bullet_image self.bullets = bullet_list self.shoot_delay = 500 self.last_shot = pygame.time.get_ticks() self.num_of_shots = 2 # enemy kamikaze boost speed self.speedy = 30 def update(self): '''update enemy movement''' if self.rect.bottom > 50 and self.rect.bottom < 130: for i in range(self.num_of_shots): self.shoot() if self.rect.bottom <= 120: self.rect.bottom += 4 if self.rect.bottom > 120 and self.rect.bottom < 140: self.rect.bottom += 1 if self.rect.bottom >= 140: self.divebomb() # if ships go off the screen, respawn them if (self.rect.top > WINDOWHEIGHT): self.rect.centerx = random.randrange(50, WINDOWWIDTH - 50) self.rect.y = random.randrange(-200, -50) |
In line 3, the EnemyShip object takes a few parameters, including enemy_image, bullet_image, sprites_list, bullet_list. The sprites_list and bullet_list we will use later to add our enemy ship and bullets (or lasers) to sprite lists. From there, the enemy is resized in line 6 and we create the rectangle object for detecting our player in the game window in line 7. Then we create other attributes specific to the player class, including initial spawn location, set up the bullet parameters, and in line 24 create a boost speed parameter for one of the enemy’s attacks.
In the update function, lines 26 – 30. We want our enemy ship to start above the game window and move down. First, we check if the bottom of the ship’s rect is > 50 and < 130. If so, it will fire two shots. In lines 32 – 37, we create the enemy movement. Our ship moves down in the y direction, slowly at first, then slows down even more, before launching itself at our player in a dive bomb attack in line 37.
If the ship misses our player and goes off screen it is respawned in lines 39 – 42.
In line 37, we use the divebomb function. For now, this is simply a function that causes the enemy to hurl itself faster towards our player.
1 2 3 |
def divebomb(self): '''divebomb flight pattern''' self.rect.bottom += self.speedy |
Create Bullets
Simply creating the enemy class, but not giving them ability to fire seems too simple. The player needs an even bigger challenge and the enemy needs to try to damage the player even more before sacrificing its own life.

Let’s first load the bullet image and then build the EnemyBullet class.
1 2 |
# load enemy bullet enemy_bullet_img = pygame.image.load('images/laser_purple.png').convert() |
Next, let’s create the bullet class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class EnemyBullet(pygame.sprite.Sprite): '''create Enemy Bullet class''' def __init__(self, bullet_image, x, y): super().__init__() # scale bullet size self.image = pygame.transform.scale(bullet_image, (8, 23)) self.image.set_colorkey(BLACK) self.rect = self.image.get_rect() # bullet position is according the player position self.rect.centerx = x self.rect.bottom = y self.speedy = 15 def update(self): '''update bullet''' self.rect.y += self.speedy # if bullet goes off bottom of window, destroy it if self.rect.bottom > WINDOWHEIGHT: self.kill() |
In the next tutorial will create a bullet class for our player (and also talk about collision detection), but for now let’s create the EnemyBullet class. We load the image, rescale it and use set_colorkey to create a transparent background for our bullet sprite. We want the bullet to seem like it is really coming from the ship. In lines 10 and 11, we assign the spawn location of the bullet centerx to x and bottom to y. x and y refer to the location of the centerx and bottom values of the ship. In the update function, the bullet will move along at speedy and if it goes off screen, we use self.kill( ) to remove it.
Back in the EnemyShip class, we create a function so that we can fire the enemy bullet.
1 2 3 4 5 6 7 8 |
def shoot(self): '''fire lasers''' current_time = pygame.time.get_ticks() if current_time - self.last_shot > self.shoot_delay: self.last_shot = current_time bullet = EnemyBullet(self.bullet_image, self.rect.centerx, self.rect.bottom) self.sprites.add(bullet) self.bullets.add(bullet) |
For the bullet, we need to consider the current time of the game so that we will be able for our enemy to fire more than one shot. We can use pygame.time to create a delay in between each shot. In line 3, we set current_time equal to pygame.time.get_ticks( ), which is the current game time. In line 4, we check the last time a shot was fired and subtract the current_time. If that difference is greater than the shoot_delay we created (if you want a bigger or shorter delay you can change the value), then we set last_shot to the new current_time, create a new EnemyBullet object, and add it to our sprite lists. We use the update function of the EnemyShip to update the bullet movement.
1 2 3 4 5 6 7 8 9 |
# create group for enemy bullets enemy_bullets = pygame.sprite.Group() # create group for enemies enemy_ships = pygame.sprite.Group() for i in range(2): enemy_ship = EnemyShip(enemy_img, enemy_bullet_img, all_active_sprites, enemy_bullets) all_active_sprites.add(enemy_ship) enemy_ships.add(enemy_ship) |
Finally, in the main while loop, create groups for both our enemy_bullets and enemy_ships. In lines 6 – 9, we want to create 2 enemy ships, and add them to the sprite lists.

Current state of game:

Summary
This time, we looked at improving our game play by creating different kinds of enemy AI. However, we still have quite some way to go. In the next tutorial, we are going to give the player bullets to fight back with and add collision detection to our game.
Please check out Part 1 if you are interested about the gameplay of our space shooter.
Please leave a comment down below to let me know what you would be interested in seeing in future posts or how I can improve my blog posts for you.