
Enemy AI is an important aspect of any game. Graphics, story, and music can all be drowned out if your game contains some wonky or useless AI or gameplay. Sometimes graphics takes a backseat and the developer can create a memorable experience from gameplay and simple graphics. When you decide to make your first game, think about the users’ experience. Try and think about what adventure you want them to have and put yourself in their shoes.
The AI in games has gotten better over the years and will continue to improve. There have been some amazing games recently with fantastic and seemingly intelligent AI, or with experiences that adapt to the player based upon their decisions within the game. Even the enemy AI can be complemented by awesome gameplay techniques or incredible environments that add to the experience.
This week we’re back to continue developing our retro arcade style space shooter (that’s a mouth full). In Part 1, we talked about getting setup in Pygame and different types of movement styles, and in Part 2, we discussed how to import images. Please refer back to those tutorials if you have any questions while going through today’s code.
In part 3, we’re going to use Pygame to:
1. Get our spaceship to move,
2. Create enemies, load multiple enemy sprites, and detect to see if they go out of bounds. We will also begin to build simple AI for the enemies,
3. Learn how to rotate sprites in our game. [UPDATE: Dec. 03, 2018]
THE CODE FOR THIS GAME CAN BE FOUND ON GITHUB.

Player Movement
Let’s first combine parts of the code and some of the ideas from Parts 1 and 2 and get our spaceship to move. Open the space_shooter.py file we created in the last tutorial.
In Part 2, we loaded and drew the image to the screen. This time we are going to create a Player class like we did back in Part 1. Using the arrow keys, the player will be able to move left, right, down and up.
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 |
class Player(pygame.sprite.Sprite): def __init__(self, image): super().__init__() # scale player image self.image = pygame.transform.scale(image, (75, 75)) self.image.set_colorkey(BLACK) self.rect = self.image.get_rect() # player starting location self.rect.centerx = WINDOWWIDTH / 2 self.rect.bottom = WINDOWHEIGHT - 10 # other player attributes self.speedx = 0 self.speedy = 0 def update(self): '''update the player''' # make player static in screen by default self.speedx = 0 self.speedy = 0 # then check if there is event handling for arrow keys keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: self.speedx = -5 if keys[pygame.K_RIGHT]: self.speedx = +5 if keys[pygame.K_UP]: self.speedy = -5 if keys[pygame.K_DOWN]: self.speedy = +5 |
Then, we will check boundary conditions to make sure the player doesn’t go outside of the window and update the player movement.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# boundary checking if self.rect.right > WINDOWWIDTH: self.rect.right = WINDOWWIDTH if self.rect.left < 0: self.rect.left = 0 if self.rect.top < 200: self.rect.top = 200 if self.rect.bottom > WINDOWHEIGHT: self.rect.bottom = WINDOWHEIGHT # update movement self.rect.x += self.speedx self.rect.y += self.speedy |
Create a Player object in main( ) and add it to the all_active_sprites list.
1 2 3 4 5 6 7 |
player_img = pygame.image.load('images/spaceship.png').convert() # create list to store all sprites all_active_sprites = pygame.sprite.Group() player = Player(player_img) all_active_sprites.add(player) |
Here is what our ship looks like moving around in the window.

Let’s Get to Know Our Enemies
What do we want our gameplay to be like? Space is a vast empty, uh, space with planets, stars and asteroids. We are going to create a game where our lonely ship is flying through an asteroid field, dodging or shooting down asteroids and other enemy ships. In this post we will discuss how to implement the asteroids and get the ship moving. In future posts, we will add more realistic movement to them.
Let’s take a look at our asteroid sprites. They are all of different sizes, shapes, colors, and textures to create a little more visual realism in our game.
In the main( ) function of our space_shooter.py file, we need to load the images for our asteroids.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
asteroid_images = [] asteroid_list = [ 'asteroid_medium2.png', 'asteroid_medium1.png', 'asteroid_medium3.png', 'asteroid_big1.png', 'asteroid_tiny.png' ] for image in asteroid_list: asteroid_images.append(pygame.image.load(path.join(img_dir, image)).convert_alpha()) # create list for asteroids asteroids = pygame.sprite.Group() |
In line 1, we create an empty list that will hold all of the asteroid images. In lines 2 – 8, we load all of the image names into a list that we use in lines 10 – 11 to append to the asteroid_images list. Note in line 11 how path.join is used to connect the image file location and each image name in asteroid_list. At the top of our file, add the os module:
1 2 |
from os import path img_dir = path.join(path.dirname(__file__), 'images') |
Now, let’s create the Asteroid class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Asteroid(pygame.sprite.Sprite): '''create Asteroid class''' def __init__(self, asteroid_img, all_sprites, asteroid_sprites): super().__init__() self.image = random.choice(asteroid_img) self.rect = self.image.get_rect() # set spawn position self.rect.x = random.randrange(0, WINDOWWIDTH - self.rect.width) self.rect.y = random.randrange(-150, -100) # set asteroid speed, speedx is created to add more random movements self.speedy = random.randrange(5, 20) self.speedx = random.randrange(-3, 3) def update(self): '''update asteroids''' self.rect.y += self.speedy self.rect.x += self.speedx |
Here, the Asteroid class is created and in line 5 we randomly select asteroids from the asteroid_img list to create a little more realism. Asteroids are spawned above the game window in line 10 and then move down the window towards our ship.
1 2 3 4 |
for i in range(8): new_asteroid = Asteroid(asteroid_images, all_active_sprites, asteroids) all_active_sprites.add(new_asteroid) asteroids.add(new_asteroid) |
Above the while True main game loop, add code that will create asteroids and hurl them towards the ship.
Unfortunately, once the asteroids go outside of the window they don’t re-spawn. We need to create code in the Asteroid class that checks if the asteroids are outside of the game window, and if so, re-spawns new asteroids above the top of the game window.
1 2 3 4 5 6 7 8 |
def update(self): '''update asteroids''' #######CODE NOT SHOWN####### # if asteroids go off the screen, respawn asteroids if (self.rect.top > WINDOWHEIGHT + 10) or (self.rect.left < -self.rect.width) or (self.rect.right > WINDOWWIDTH + self.rect.width): self.rect.x = random.randrange(0, WINDOWWIDTH - self.rect.width) self.rect.y = random.randrange(-100, -20) self.speedy = random.randrange(3, 8) |
That’s better.

Rotation
[UPDATE: Dec. 03, 2018]
In the above gif, you can see that our asteroids are just hovering in space towards our ship. But it doesn’t look realistic. So let’s add rotation to our asteroid objects. (Which led me to search if they do really rotate in space. Fact: They actually kind of tumble.)
1 2 3 4 5 6 7 8 9 |
class Asteroid(pygame.sprite.Sprite): '''create Asteroid class''' def __init__(self, asteroid_img, all_sprites, asteroid_sprites): super().__init__() ###### OMITTED ###### # add rotation elements to the asteroids to make them look more realistic self.angle = 0 # the amount of rotation self.rotation_speed = random.randrange(-7, 7) self.last_update = pygame.time.get_ticks() # time for rotating asteroid |
In our Asteroid class, let’s add some new attributes. self.angle is the number of degrees we want the asteroid image to rotate every time it updates, self.rotation_speed is fast we want them to rotate.
1 2 3 4 5 6 7 8 9 10 11 |
def rotate(self): '''handle rotation of asteroids''' current_time = pygame.time.get_ticks() if current_time - self.last_update > 50: self.last_update = current_time # reset current time self.angle = (self.angle + self.rotation_speed) % 360 new_image = pygame.transform.rotate(self.image_orig, self.angle) old_center = self.rect.center self.image = new_image self.rect = self.image.get_rect() self.rect.center = old_center |
In line 3, we check the current_time, and if the last time that asteroid rotated is greater than 50, we update the current time in line 5. Then, we need to keep track of the angle of rotation on line 6. In line 7, we ensure that the angle does not increase beyond a 360 degree rotation. We then rotate the asteroid in line 8, and keep updating the image for the asteroid.
1 2 3 4 5 |
def update(self): '''update asteroids''' self.rotate() self.rect.y += self.speedy self.rect.x += self.speedx |
Finally, we add the rotate( ) function in update.
Summary
In this post, we looked at how to create a simple enemy AI using asteroids for our space shooter game. In the next post, we will create small enemy ships and add rotation to our asteroids.
If you have any questions, please leave a comment below.
THE CODE FOR THIS GAME CAN BE FOUND ON GITHUB.
References
kisspng.com: Tons of free, downloadable pngs.