alien.py
import pygame
from settings import BULLET_SIZE
from bullet import Bullet
class Alien(pygame.sprite.Sprite):
def __init__(self, pos, size, row_num):
super().__init__()
self.x = pos[0]
self.y = pos[1]
# alien info
img_path = f'assets/aliens/{row_num}.png'
self.image = pygame.image.load(img_path)
self.image = pygame.transform.scale(self.image, (size, size))
self.rect = self.image.get_rect(topleft = pos)
self.mask = pygame.mask.from_surface(self.image)
self.move_speed = 5
self.to_direction = "right"
# alien status
self.bullets = pygame.sprite.GroupSingle()
def move_left(self):
self.rect.x -= self.move_speed
def move_right(self):
self.rect.x += self.move_speed
def move_bottom(self):
self.rect.y += self.move_speed
def _shoot(self):
specific_pos = (self.rect.centerx - (BULLET_SIZE // 2), self.rect.centery)
self.bullets.add(Bullet(specific_pos, BULLET_SIZE, "enemy"))
def update(self):
self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y))
bullet.py
import pygame
from settings import BULLET_SPEED, HEIGHT
class Bullet(pygame.sprite.Sprite):
def __init__(self, pos, size, side):
super().__init__()
self.x = pos[0]
self.y = pos[1]
# bullet info
img_path = f'assets/bullet/{side}-bullet.png'
self.image = pygame.image.load(img_path)
self.image = pygame.transform.scale(self.image, (size, size))
self.rect = self.image.get_rect(topleft = pos)
self.mask = pygame.mask.from_surface(self.image)
# different bullet movement direction for both player and enemy (alien)
if side == "enemy":
self.move_speed = BULLET_SPEED
elif side == "player":
self.move_speed = (- BULLET_SPEED)
def _move_bullet(self):
self.rect.y += self.move_speed
def update(self):
self._move_bullet()
self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y))
# delete the bullet if it get through out of the screen
if self.rect.bottom <= 0 or self.rect.top >= HEIGHT:
self.kill()
display.py
import pygame
from settings import WIDTH, HEIGHT, SPACE, FONT_SIZE, EVENT_FONT_SIZE
pygame.font.init()
class Display:
def __init__(self, screen):
self.screen = screen
self.score_font = pygame.font.SysFont("monospace", FONT_SIZE)
self.level_font = pygame.font.SysFont("impact", FONT_SIZE)
self.event_font = pygame.font.SysFont("impact", EVENT_FONT_SIZE)
self.text_color = pygame.Color("blue")
self.event_color = pygame.Color("red")
def show_life(self, life):
life_size = 30
img_path = "assets/life/life.png"
life_image = pygame.image.load(img_path)
life_image = pygame.transform.scale(life_image, (life_size, life_size))
life_x = SPACE // 2
if life != 0:
for life in range(life):
self.screen.blit(life_image, (life_x, HEIGHT + (SPACE // 2)))
life_x += life_size
def show_score(self, score):
score_x = WIDTH // 3
score = self.score_font.render(f'score: {score}', True, self.text_color)
self.screen.blit(score, (score_x, (HEIGHT + (SPACE // 2))))
def show_level(self, level):
level_x = WIDTH // 3
level = self.level_font.render(f'Level {level}', True, self.text_color)
self.screen.blit(level, (level_x * 2, (HEIGHT + (SPACE // 2))))
def game_over_message(self):
message = self.event_font.render('GAME OVER!!', True, self.event_color)
self.screen.blit(message, ((WIDTH // 3) - (EVENT_FONT_SIZE // 2), (HEIGHT // 2) - (EVENT_FONT_SIZE // 2)))
main.py
import pygame, sys
from settings import WIDTH, HEIGHT, NAV_THICKNESS
from world import World
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT + NAV_THICKNESS))
pygame.display.set_caption("Space Invader")
class Main:
def __init__(self, screen):
self.screen = screen
self.FPS = pygame.time.Clock()
def main(self):
world = World(self.screen)
while True:
self.screen.fill("black")
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
world.player_move(attack = True)
world.player_move()
world.update()
pygame.display.update()
self.FPS.tick(30)
if __name__ == "__main__":
play = Main(screen)
play.main()
settings.py
WIDTH, HEIGHT = 720, 450
SPACE = 30
FONT_SIZE = 20
EVENT_FONT_SIZE = 60
NAV_THICKNESS = 50
CHARACTER_SIZE = 30
PLAYER_SPEED = 10
ENEMY_SPEED = 1
BULLET_SPEED = 15 # for both sides
BULLET_SIZE = 10
ship.py
import pygame
from settings import PLAYER_SPEED, BULLET_SIZE
from bullet import Bullet
class Ship(pygame.sprite.Sprite):
def __init__(self, pos, size):
super().__init__()
self.x = pos[0]
self.y = pos[1]
# ship info
img_path = 'assets/ship/ship.png'
self.image = pygame.image.load(img_path)
self.image = pygame.transform.scale(self.image, (size, size))
self.rect = self.image.get_rect(topleft = pos)
self.mask = pygame.mask.from_surface(self.image)
self.ship_speed = PLAYER_SPEED
# ship status
self.life = 3
self.player_bullets = pygame.sprite.Group()
def move_left(self):
self.rect.x -= self.ship_speed
def move_up(self):
self.rect.y -= self.ship_speed
def move_right(self):
self.rect.x += self.ship_speed
def move_bottom(self):
self.rect.y += self.ship_speed
def _shoot(self):
specific_pos = (self.rect.centerx - (BULLET_SIZE // 2), self.rect.y)
self.player_bullets.add(Bullet(specific_pos, BULLET_SIZE, "player"))
def update(self):
self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y))
world.py
import pygame
from ship import Ship
from alien import Alien
from settings import HEIGHT, WIDTH, ENEMY_SPEED, CHARACTER_SIZE, BULLET_SIZE, NAV_THICKNESS
from bullet import Bullet
from display import Display
class World:
def __init__(self, screen):
self.screen = screen
self.player = pygame.sprite.GroupSingle()
self.aliens = pygame.sprite.Group()
self.display = Display(self.screen)
self.game_over = False
self.player_score = 0
self.game_level = 1
self._generate_world()
def _generate_aliens(self):
# generate opponents
alien_cols = (WIDTH // CHARACTER_SIZE) // 2
alien_rows = 3
for y in range(alien_rows):
for x in range(alien_cols):
my_x = CHARACTER_SIZE * x
my_y = CHARACTER_SIZE * y
specific_pos = (my_x, my_y)
self.aliens.add(Alien(specific_pos, CHARACTER_SIZE, y))
# create and add player to the screen
def _generate_world(self):
# create the player's ship
player_x, player_y = WIDTH // 2, HEIGHT - CHARACTER_SIZE
center_size = CHARACTER_SIZE // 2
player_pos = (player_x - center_size, player_y)
self.player.add(Ship(player_pos, CHARACTER_SIZE))
self._generate_aliens()
def add_additionals(self):
# add nav bar
nav = pygame.Rect(0, HEIGHT, WIDTH, NAV_THICKNESS)
pygame.draw.rect(self.screen, pygame.Color("gray"), nav)
# render player's life, score and game level
self.display.show_life(self.player.sprite.life)
self.display.show_score(self.player_score)
self.display.show_level(self.game_level)
def player_move(self, attack = False):
keys = pygame.key.get_pressed()
if keys[pygame.K_a] and not self.game_over or keys[pygame.K_LEFT] and not self.game_over:
if self.player.sprite.rect.left > 0:
self.player.sprite.move_left()
if keys[pygame.K_d] and not self.game_over or keys[pygame.K_RIGHT] and not self.game_over:
if self.player.sprite.rect.right < WIDTH:
self.player.sprite.move_right()
if keys[pygame.K_w] and not self.game_over or keys[pygame.K_UP] and not self.game_over:
if self.player.sprite.rect.top > 0:
self.player.sprite.move_up()
if keys[pygame.K_s] and not self.game_over or keys[pygame.K_DOWN] and not self.game_over:
if self.player.sprite.rect.bottom < HEIGHT:
self.player.sprite.move_bottom()
# game restart button
if keys[pygame.K_r]:
self.game_over = False
self.player_score = 0
self.game_level = 1
for alien in self.aliens.sprites():
alien.kill()
self._generate_world()
if attack and not self.game_over:
self.player.sprite._shoot()
def _detect_collisions(self):
# checks if player bullet hits the enemies (aliens)
player_attack_collision = pygame.sprite.groupcollide(self.aliens, self.player.sprite.player_bullets, True, True)
if player_attack_collision:
self.player_score += 10
# checks if the aliens' bullet hit the player
for alien in self.aliens.sprites():
alien_attack_collision = pygame.sprite.groupcollide(alien.bullets, self.player, True, False)
if alien_attack_collision:
self.player.sprite.life -= 1
break
# checks if the aliens hit the player
alien_to_player_collision = pygame.sprite.groupcollide(self.aliens, self.player, True, False)
if alien_to_player_collision:
self.player.sprite.life -= 1
def _alien_movement(self):
move_sideward = False
move_forward = False
for alien in self.aliens.sprites():
if alien.to_direction == "right" and alien.rect.right < WIDTH or alien.to_direction == "left" and alien.rect.left > 0:
move_sideward = True
move_forward = False
else:
move_sideward = False
move_forward = True
alien.to_direction = "left" if alien.to_direction == "right" else "right"
break
for alien in self.aliens.sprites():
if move_sideward and not move_forward:
if alien.to_direction == "right":
alien.move_right()
if alien.to_direction == "left":
alien.move_left()
if not move_sideward and move_forward:
alien.move_bottom()
def _alien_shoot(self):
for alien in self.aliens.sprites():
if (WIDTH - alien.rect.x) // CHARACTER_SIZE == (WIDTH - self.player.sprite.rect.x) // CHARACTER_SIZE:
alien._shoot()
break
def _check_game_state(self):
# check if game over
if self.player.sprite.life <= 0:
self.game_over = True
self.display.game_over_message()
for alien in self.aliens.sprites():
if alien.rect.top >= HEIGHT:
self.game_over = True
self.display.game_over_message()
break
# check if next level
if len(self.aliens) == 0 and self.player.sprite.life > 0:
self.game_level += 1
self._generate_aliens()
for alien in self.aliens.sprites():
alien.move_speed += self.game_level - 1
def update(self):
# detecting if bullet, alien, and player group is colliding
self._detect_collisions()
# allows the aliens to move
self._alien_movement()
# allows alien to shoot the player
self._alien_shoot()
# bullets rendering
self.player.sprite.player_bullets.update()
self.player.sprite.player_bullets.draw(self.screen)
[alien.bullets.update() for alien in self.aliens.sprites()]
[alien.bullets.draw(self.screen) for alien in self.aliens.sprites()]
# player ship rendering
self.player.update()
self.player.draw(self.screen)
# alien rendering
self.aliens.draw(self.screen)
# add nav
self.add_additionals()
# checks game state
self._check_game_state()