""" --------------------------------------------------- Project: Platformer Standard: 91896 School: Tauranga Boys' College Author: Levi Mitchell Data: March 2020 Python: 3.7.4 --------------------------------------------------- """ import pygame from pygame.locals import * from pygame import mixer import pickle from os import path pygame.mixer.pre_init(44100, -16, 2, 512) mixer.init() pygame.init() clock = pygame.time.Clock() fps = 60 screen_width = 1000 screen_height = 1000 screen = pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption('Platformer') #define font font = pygame.font.SysFont('Bauhaus 93', 70) font_score = pygame.font.SysFont('Bauhaus 93', 30) font_small = pygame.font.SysFont('Bauhaus 93', 50) font_smaller = pygame.font.SysFont('Bauhaus 93', 35) font_smallerer = pygame.font.SysFont('Bauhaus 93', 25) #define game variables tile_size = 50 game_over = 0 main_menu = True level = 1 max_levels = 7 bullet_count = 5 bullets = [] # List to store bullets #define colours white = (255, 255, 255) blue = (0, 0, 255) black = (0,0,0) #load images restart_img = pygame.image.load('img/restart_btn.png') start_img = pygame.image.load('img/start_btn.png') exit_img = pygame.image.load('img/exit_btn.png') bg_img = pygame.image.load('img/bg_1.png') #load sounds pygame.mixer.music.load('music/music.mp3') pygame.mixer.music.set_volume(0.05) pygame.mixer.music.play(-1, 0.0, 5000) ammo_pickup = pygame.mixer.Sound('music/ammo_p.wav') ammo_pickup.set_volume(0.2) game_over_fx = pygame.mixer.Sound('music/game_over.wav') game_over_fx.set_volume(0.5) bullet_sound = pygame.mixer.Sound('music/bullet.wav') bullet_sound.set_volume(0.2) def draw_text(text, font, text_col, x, y): img = font.render(text, True, text_col) screen.blit(img, (x, y)) #function to reset level def reset_level(level): player.reset(100, screen_height - 130) enemy_group.empty() spike_group.empty() exit_group.empty() #load in level data and create world if path.exists(f'level{level}_data'): pickle_in = open(f'level{level}_data', 'rb') world_data = pickle.load(pickle_in) world = World(world_data) return world class Button(): def __init__(self, x, y, image): self.image = image self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.clicked = False def draw(self): action = False #get mouse position pos = pygame.mouse.get_pos() #check mouseover and clicked conditions if self.rect.collidepoint(pos): if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False: action = True self.clicked = True if pygame.mouse.get_pressed()[0] == 0: self.clicked = False #draw button screen.blit(self.image, self.rect) return action class Bullet: def __init__(self, x, y, radius, colour, facing): self.x = x self.y = y self.radius = radius self.colour = colour self.facing = facing self.vel = 6 # speed of projectile self.rect = pygame.Rect(self.x - self.radius, self.y - self.radius, self.radius * 2, self.radius * 2) self.active = True def update(self): if self.facing == 1: self.x += self.vel else: self.x -= self.vel self.rect.x = self.x # Check for collision with enemies collision_list = pygame.sprite.spritecollide(self, enemy_group, False) for enemy in collision_list: enemy.take_damage() self.active = False # Deactivate the bullet after hitting an enemy # Check for collision with tiles for tile in world.tile_list: if tile[1].colliderect(self.rect): self.active = False def draw(self, screen): pygame.draw.circle(screen, self.colour, (self.x, self.y), self.radius) class Player(): def __init__(self, x, y): self.reset(x, y) def update(self, game_over): dx = 0 dy = 0 walk_cooldown = 3 if game_over == 0: # get keypresses key = pygame.key.get_pressed() if key[pygame.K_SPACE] and self.jumped == False and self.in_air == False: self.vel_y = -15 self.jumped = True if key[pygame.K_SPACE] == False: self.jumped = False if key[pygame.K_LEFT] or key[pygame.K_a]: dx -= 5 self.counter += 1 self.direction = -1 if key[pygame.K_RIGHT] or key[pygame.K_d]: dx += 5 self.counter += 1 self.direction = 1 if not (key[pygame.K_LEFT] or key[pygame.K_a]) and not (key[pygame.K_RIGHT] or key[pygame.K_d]): self.counter = 0 self.index = 0 # Reset to standing image based on direction if self.direction == 1: self.image = self.images_right[self.index] elif self.direction == -1: self.image = self.images_left[self.index] # handle animation if self.counter > walk_cooldown: self.counter = 0 self.index += 1 if self.index >= len(self.images_right): self.index = 0 if self.direction == 1: self.image = self.images_right[self.index] elif self.direction == -1: self.image = self.images_left[self.index] # add gravity self.vel_y += 1 if self.vel_y > 10: self.vel_y = 10 dy += self.vel_y # check for collision self.in_air = True for tile in world.tile_list: # check for collision in x direction if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): dx = 0 # check for collision in y direction if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): # check if below the ground i.e. jumping if self.vel_y < 0: dy = tile[1].bottom - self.rect.top self.vel_y = 0 # check if above the ground i.e. falling elif self.vel_y >= 0: dy = tile[1].top - self.rect.bottom self.vel_y = 0 self.in_air = False # check for collision with enemies if pygame.sprite.spritecollide(self, enemy_group, False): game_over = -1 game_over_fx.play() # check for collision with spikes if pygame.sprite.spritecollide(self, spike_group, False): game_over = -1 game_over_fx.play() # check for collision with exit if pygame.sprite.spritecollide(self, exit_group, False): game_over = 1 # update player coordinates self.rect.x += dx self.rect.y += dy elif game_over == -1: self.image = self.dead_image draw_text('GAME OVER!', font, blue, (screen_width // 2) - 150, screen_height // 2) if self.rect.y > 200: self.rect.y -= 5 # draw player onto screen screen.blit(self.image, self.rect) # pygame.draw.rect(screen, (255, 255, 255), self.rect, 2) return game_over def reset(self, x, y): self.images_right = [] self.images_left = [] self.index = 0 self.counter = 0 for num in range(1, 5): img_right = pygame.image.load(f'img/main{num}.png') img_right = pygame.transform.scale(img_right, (40, 68)) img_left = pygame.transform.flip(img_right, True, False) self.images_right.append(img_right) self.images_left.append(img_left) self.dead_image = pygame.image.load('img/ghost.png') self.image = self.images_right[self.index] self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.width = self.image.get_width() self.height = self.image.get_height() self.vel_y = 0 self.jumped = False self.direction = 0 self.in_air = True class World(): def __init__(self, data): self.tile_list = [] #load images brick_img = pygame.image.load('img/brick.png') wood_img = pygame.image.load('img/wood.png') row_count = 0 for row in data: col_count = 0 for tile in row: if tile == 1: img = pygame.transform.scale(brick_img, (tile_size, tile_size)) img_rect = img.get_rect() img_rect.x = col_count * tile_size img_rect.y = row_count * tile_size tile = (img, img_rect) self.tile_list.append(tile) if tile == 2: img = pygame.transform.scale(wood_img, (tile_size, tile_size)) img_rect = img.get_rect() img_rect.x = col_count * tile_size img_rect.y = row_count * tile_size tile = (img, img_rect) self.tile_list.append(tile) if tile == 3: spike = Spike(col_count * tile_size, row_count * tile_size + (tile_size // 2)) spike_group.add(spike) if tile == 4: coin = Ammo(col_count * tile_size + (tile_size // 2), row_count * tile_size + (tile_size // 2)) ammo_group.add(coin) if tile == 5: exit = Exit(col_count * tile_size, row_count * tile_size - (tile_size // 2)) exit_group.add(exit) if tile == 6: enemy = Enemy(col_count * tile_size, row_count * tile_size + 15) enemy_group.add(enemy) col_count += 1 row_count += 1 def draw(self): for tile in self.tile_list: screen.blit(tile[0], tile[1]) #pygame.draw.rect(screen, (255, 255, 255), tile[1], 2) class Enemy(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.images_right = [] self.images_left = [] self.load_images() self.index = 0 self.counter = 0 self.rect = self.images_right[self.index].get_rect() self.rect.x = x self.rect.y = y self.rect.y = y + 20 # Make enemy look like they are on top of tiles self.move_direction = 1 self.move_counter = 0 self.health = 10 # Initial health value self.alive = True # Flag to check if the enemy is alive def load_images(self): for num in range(1, 5): enemy_right = pygame.image.load(f'img/enemy{num}.png') enemy_right = pygame.transform.scale(enemy_right, (40, 68)) self.images_right.append(enemy_right) enemy_left = pygame.transform.flip(enemy_right, True, False) self.images_left.append(enemy_left) def update(self): self.rect.x += self.move_direction self.move_counter += 1 # Animation handling if self.counter >= 10: # Update animation frame every 10 frames self.index += 1 if self.index >= len(self.images_right): self.index = 0 self.counter = 0 if abs(self.move_counter) > 50: self.move_direction *= -1 self.move_counter *= -1 self.counter += 1 if self.move_direction == 1: self.image = self.images_right[self.index] else: self.image = self.images_left[self.index] def take_damage(self): self.health -= 1 if self.health <= 0: self.alive = False def is_alive(self): return self.alive def draw_health_bar(self, screen): if self.alive: # Draw health bar only if the enemy is alive # Draw health bar above the enemy bar_width = 40 bar_height = 5 max_health = 10 fill = (self.health / max_health) * bar_width pygame.draw.rect(screen, (255, 0, 0), (self.rect.x, self.rect.y - 10, bar_width, bar_height)) pygame.draw.rect(screen, (0, 255, 0), (self.rect.x, self.rect.y - 10, fill, bar_height)) class Spike(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/spike.png') self.image = pygame.transform.scale(img, (tile_size, tile_size // 2)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y class Ammo(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/ammo.png') self.image = pygame.transform.scale(img, (tile_size // 2, tile_size // 2)) self.rect = self.image.get_rect() self.rect.center = (x, y) class Exit(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/exit.png') self.image = pygame.transform.scale(img, (tile_size, int(tile_size * 1.5))) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y player = Player(100, screen_height - 130) enemy_group = pygame.sprite.Group() spike_group = pygame.sprite.Group() ammo_group = pygame.sprite.Group() exit_group = pygame.sprite.Group() #create dummy ammo for showing the amount the player has score_ammo = Ammo(65, 77) ammo_group.add(score_ammo) #load in level data and create world if path.exists(f'level{level}_data'): pickle_in = open(f'level{level}_data', 'rb') world_data = pickle.load(pickle_in) world = World(world_data) #create buttons restart_button = Button(screen_width // 2 - 50, screen_height // 2 + 100, restart_img) start_button = Button(screen_width // 2 - 350, screen_height // 2, start_img) exit_button = Button(screen_width // 2 + 150, screen_height // 2, exit_img) run = True while run: clock.tick(fps) screen.blit(bg_img, (0, 0)) if main_menu == True: if exit_button.draw(): run = False if start_button.draw(): main_menu = False else: world.draw() if game_over == 0: enemy_group.update() # Update score # Check if ammo has been collected if pygame.sprite.spritecollide(player, ammo_group, True): bullet_count += 5 ammo_pickup.play() draw_text('X ' + str(bullet_count), font_score, black, 80, 70) enemy_group.draw(screen) spike_group.draw(screen) ammo_group.draw(screen) exit_group.draw(screen) for bullet in bullets: bullet.update() bullet.draw(screen) if not bullet.active: bullets.remove(bullet) # Remove inactive bullets for enemy in list(enemy_group): # Create a list of enemies to iterate over if not enemy.is_alive(): enemy_group.remove(enemy) # Remove dead enemies from the group # Draw health bars for enemies for enemy in enemy_group: enemy.draw_health_bar(screen) game_over = player.update(game_over) # If player has died if game_over == -1: if restart_button.draw(): world_data = [] world = reset_level(level) game_over = 0 bullet_count = 5 # If player has completed the level if game_over == 1: # Reset game and go to next level level += 1 if level <= max_levels: # Reset level world_data = [] world = reset_level(level) game_over = 0 else: draw_text('YOU WIN!', font_small, blue, (screen_width // 2) - 75, screen_height // 2) if restart_button.draw(): level = 1 # Reset level world_data = [] world = reset_level(level) game_over = 0 bullet_count = 0 if level == 1 and main_menu == False: draw_text('Use LEFT or RIGHT, A or D to move around', font_smaller, black, 175, 375) draw_text('Enter gates to pass the level', font_small, black, 200, 475) if level == 2: draw_text('Use SPACE to jump', font_small, black, 500, 300) if level == 3: draw_text('Spikes will kill you', font_small, black, 350, 100) if level == 4: draw_text('Enemies will kill you, shoot them 10 times to get rid of them (press F)', font_smallerer, black, 150, 200) draw_text('Your ammo is displayed in the top left', font_small, black, 100, 300) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False # Fire bullets if event.type == pygame.KEYDOWN: if bullet_count > 0 and game_over == 0: if event.key == pygame.K_f: bullet_sound.play() if player.direction == 1: # Checking direction to be fired bullets.append(Bullet(player.rect.right, player.rect.centery, 6, (0,0,0), player.direction)) bullet_count -= 1 else: bullets.append(Bullet(player.rect.left, player.rect.centery, 6, (0,0,0), player.direction)) bullet_count -= 1 pygame.display.update() pygame.quit() #Copyright (c) 2024 Levi Mitchell