#------------------------------------------------- # Project: Pygame Platformer # Standard: 91883? # School: Tauranga Boys' College # Author: Hayden De Rohan # Date: 22/07/24 #------------------------------------------- import pygame, os from pygame.locals import * from pygame import mixer import pickle from os import path #left shift to Dash! # Set working directory os.chdir('w:/11/hayden.derohan/Programming/91883 - Assignment/Platformer_Assignment') # Print current working directory print("Current working directory:", os.getcwd()) pygame.mixer.pre_init(44100, -16, 2, 512) mixer.init() pygame.init() clock = pygame.time.Clock() fps = 60 #Window size 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) key_font = pygame.font.SysFont('Bauhaus 93', 50) #define game variables tile_size = 50 game_over = 0 main_menu = True level = 3 MAX_LEVELS = 5 score = 0 key_collected = False #define colours white = (255, 255, 255) blue = (0, 0, 255) yellow = (255,255,0) #load images bg_Newsprites = pygame.image.load('Newsprites/Cave.png') restart_Newsprites = pygame.image.load('Newsprites/restart_btn.png') start_Newsprites = pygame.image.load('Newsprites/start_btn.png') exit_Newsprites = pygame.image.load('Newsprites/exit_btn.png') dirt_Newsprites = pygame.image.load('Newsprites/Ground.png') grass_Newsprites = pygame.image.load('Newsprites/Ground2.png') torch = pygame.image.load('Newsprites/Torch.png') #load sounds pygame.mixer.music.load('Newsprites/music.wav') pygame.mixer.music.play(-1, 0.0, 5000) coin_fx = pygame.mixer.Sound('Newsprites/coin.wav') coin_fx.set_volume(0.5) jump_fx = pygame.mixer.Sound('Newsprites/jump.wav') jump_fx.set_volume(0.5) game_over_fx = pygame.mixer.Sound('Newsprites/game_over.wav') game_over_fx.set_volume(0.5) #Creates any type of text def draw_text(text, font, text_col, x, y): Newsprites = font.render(text, True, text_col) screen.blit(Newsprites, (x, y)) #Reset level def reset_level(level): player.reset(100, screen_height - 130) #Clear the level Bat_group.empty() platform_group.empty() coin_group.empty() lava_group.empty() exit_group.empty() Key_group.empty() exitkey_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) # Create dummy coin for showing the score score_coin = Coin(tile_size // 2, tile_size // 2) coin_group.add(score_coin) return world #Creates buttons 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 #Player class Player(): def __init__(self, x, y): self.reset(x, y) def reset(self, x, y): self.images_right = [] self.images_left = [] self.index = 0 self.counter = 0 for num in range(1, 5): Newsprites_right = pygame.image.load(f'Newsprites/guy{num}.png') Newsprites_right = pygame.transform.scale(Newsprites_right, (40, 80)) Newsprites_left = pygame.transform.flip(Newsprites_right, True, False) self.images_right.append(Newsprites_right) self.images_left.append(Newsprites_left) #Player attributes self.dead_image = pygame.image.load('Newsprites/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 # Dash attributes self.is_dashing = False self.dash_speed = 25 self.dash_time = 5 self.dash_counter = 0 self.dash_cooldown = 0 self.dash_cooldown_time = 90 # 1.5 seconds # Double jump attributes self.doublejump_ready = True def handle_dash(self): # Dash handler if self.is_dashing: if self.dash_counter > 0: self.dash_counter -= 1 else: self.is_dashing = False self.dash_cooldown = self.dash_cooldown_time if self.dash_cooldown > 0: self.dash_cooldown -= 1 # -1 per frame def update(self, game_over, key_collected): dx = 0 dy = 0 walk_cooldown = 5 col_thresh = 20 if game_over == 0: # Handle dash self.handle_dash() # Get keypresses and move player accordingly key = pygame.key.get_pressed() if key[pygame.K_SPACE] and not self.jumped and not self.in_air: self.vel_y = -14 self.jumped = True self.doublejump_ready = True # Reset double jump when initial jump is performed elif key[pygame.K_SPACE] and self.in_air and self.doublejump_ready and not self.jumped: self.vel_y = -10 self.doublejump_ready = False # Disable double jump after it's used self.jumped = True # prevent multiple jumps if not key[pygame.K_SPACE]: self.jumped = False if key[pygame.K_LEFT]: dx -= 5 self.counter += 1 self.direction = -1 if key[pygame.K_RIGHT]: dx += 5 self.counter += 1 self.direction = 1 if not key[pygame.K_LEFT] and not key[pygame.K_RIGHT]: self.counter = 0 self.index = 0 if self.direction == 1: self.image = self.images_right[self.index] if self.direction == -1: self.image = self.images_left[self.index] # Dash logic if key[pygame.K_LSHIFT] and not self.is_dashing and self.dash_cooldown == 0: print("Dash initiated") self.is_dashing = True self.dash_counter = self.dash_time # 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] if 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 if self.is_dashing: if self.direction == 1: dx += self.dash_speed else: dx -= self.dash_speed # Check for collision self.in_air = True for tile in world.tile_list: # Skip torch blocks if tile[2] == 9: continue # 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, Bat_group, False): game_over = -1 game_over_fx.play() # Check for collision with lava if pygame.sprite.spritecollide(self, lava_group, False): game_over = -1 game_over_fx.play() # Check for collision with exit if pygame.sprite.spritecollide(self, exit_group, False): key_collected = False print("level{}_data loaded".format(level)) game_over = 1 # Check for collision with exit with key if pygame.sprite.spritecollide(self, exitkey_group, False): if key_collected: key_collected = False print("Level{}_Data loaded".format(level)) game_over = 1 pygame.sprite.spritecollide(self, exitkey_group, True) # Remove the exitkey sprite now else: print("Got no key!") draw_text('Collect the key first!', key_font, yellow, (screen_width / 2) - 215, screen_height / 2) # Check for collision with platforms for platform in platform_group: # Collision in the x direction if platform.rect.colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): dx = 0 # Collision in the y direction if platform.rect.colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): # Check if below platform if abs((self.rect.top + dy) - platform.rect.bottom) < col_thresh: self.vel_y = 0 dy = platform.rect.bottom - self.rect.top # Check if above platform elif abs((self.rect.bottom + dy) - platform.rect.top) < col_thresh: self.rect.bottom = platform.rect.top - 1 self.in_air = False dy = 0 # Move sideways with the platform if platform.move_x != 0: self.rect.x += platform.move_direction # Update player coordinates self.rect.x += dx self.rect.y += dy #Player has lost elif game_over == -1: self.image = self.dead_image draw_text('GAME OVER!', font, white, (screen_width // 2) - 200, screen_height // 2) if self.rect.y > -100: self.rect.y -= 2 # Draw player onto screen screen.blit(self.image, self.rect) return game_over, key_collected #Create the maps class World(): def __init__(self, data): self.tile_list = [] row_count = 0 for row in data: col_count = 0 for tile in row: #Creats all of the games tiles if tile == 1: Newsprites = pygame.transform.scale(dirt_Newsprites, (tile_size, tile_size)) Newsprites_rect = Newsprites.get_rect() Newsprites_rect.x = col_count * tile_size Newsprites_rect.y = row_count * tile_size tile = (Newsprites, Newsprites_rect, 1) self.tile_list.append(tile) if tile == 2: Newsprites = pygame.transform.scale(grass_Newsprites, (tile_size, tile_size)) Newsprites_rect = Newsprites.get_rect() Newsprites_rect.x = col_count * tile_size Newsprites_rect.y = row_count * tile_size tile = (Newsprites, Newsprites_rect, 2) self.tile_list.append(tile) if tile == 3: Bat = Enemy(col_count * tile_size, row_count * tile_size - 5) Bat_group.add(Bat) if tile == 4: platform = Platform(col_count * tile_size, row_count * tile_size, 1, 0) platform_group.add(platform) if tile == 5: platform = Platform(col_count * tile_size, row_count * tile_size, 0, 1) platform_group.add(platform) if tile == 6: lava = Lava(col_count * tile_size, row_count * tile_size + (tile_size // 2)) lava_group.add(lava) if tile == 7: coin = Coin(col_count * tile_size + (tile_size // 2), row_count * tile_size + (tile_size // 2)) coin_group.add(coin) if tile == 8: exit = Exit(col_count * tile_size, row_count * tile_size - (tile_size // 2)) exit_group.add(exit) if tile == 9: Newsprites = pygame.transform.scale(torch, (tile_size, tile_size)) Newsprites_rect = Newsprites.get_rect() Newsprites_rect.x = col_count * tile_size Newsprites_rect.y = row_count * tile_size tile = (Newsprites, Newsprites_rect, 9) self.tile_list.append(tile) if tile == 10: key = Key((col_count * tile_size) + 7.5, (row_count * tile_size) - 25) Key_group.add(key) if tile == 11: exit = Exitkey(col_count * tile_size, row_count * tile_size - (tile_size // 2)) exitkey_group.add(exit) col_count += 1 row_count += 1 def draw(self): for tile in self.tile_list: screen.blit(tile[0], tile[1]) #Enemy sprite class Enemy(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load('Newsprites/Bat.png') self.image = pygame.transform.scale(self.image, (60, 50)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.move_direction = 1 self.move_counter = 0 def update(self): self.rect.x += self.move_direction self.move_counter += 1 if abs(self.move_counter) > 75: self.move_direction *= -1 self.move_counter *= -1 #key sprite class Key(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load('Newsprites/Key.png') self.image = pygame.transform.scale(self.image, (30, 40)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.move_direction = 1 self.move_counter = 0 def update(self): self.rect.y += self.move_direction self.move_counter += 1 if abs(self.move_counter) > 5: self.move_direction *= -1 self.move_counter *= -1 #Platform sprite class Platform(pygame.sprite.Sprite): def __init__(self, x, y, move_x, move_y): pygame.sprite.Sprite.__init__(self) Newsprites = pygame.image.load('Newsprites/Ground2.png') self.image = pygame.transform.scale(Newsprites, (tile_size, tile_size // 2)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.move_counter = 0 self.move_direction = 1 self.move_x = move_x self.move_y = move_y def update(self): self.rect.x += self.move_direction * self.move_x self.rect.y += self.move_direction * self.move_y self.move_counter += 1 if abs(self.move_counter) > 50: self.move_direction *= -1 self.move_counter *= -1 #Lava sprite class Lava(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) Newsprites = pygame.image.load('Newsprites/lava.png') self.image = pygame.transform.scale(Newsprites, (tile_size, tile_size // 2)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y #Coin sprite class Coin(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) Newsprites = pygame.image.load('Newsprites/Gem.png') self.image = pygame.transform.scale(Newsprites, (tile_size // 1.38, tile_size // 1.55)) self.rect = self.image.get_rect() self.rect.center = (x, y) #Exit door sprite class Exit(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) Newsprites = pygame.image.load('Newsprites/exit.png') self.image = pygame.transform.scale(Newsprites, (tile_size, int(tile_size * 1.5))) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y #The sprite for the door the requires a key class Exitkey(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) Newsprites = pygame.image.load('Newsprites/exitkey.png') self.image = pygame.transform.scale(Newsprites, (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) #Sprite groups Bat_group = pygame.sprite.Group() platform_group = pygame.sprite.Group() lava_group = pygame.sprite.Group() coin_group = pygame.sprite.Group() exit_group = pygame.sprite.Group() Key_group = pygame.sprite.Group() exitkey_group = pygame.sprite.Group() #create dummy coin for showing the score score_coin = Coin(tile_size // 2, tile_size // 2) coin_group.add(score_coin) #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) key_collected = False #create buttons restart_button = Button(screen_width // 2 - 50, screen_height // 2 + 150, restart_Newsprites) start_button = Button(screen_width // 2 - 350, screen_height // 2, start_Newsprites) exit_button = Button(screen_width // 2 + 150, screen_height // 2, exit_Newsprites) # Update the main game loop to ensure proper reset run = True while run: clock.tick(fps) screen.blit(bg_Newsprites, (0, 0)) #Main menu buttons if main_menu: if exit_button.draw(): run = False if start_button.draw(): main_menu = False else: world.draw() #If player is still alive if game_over == 0: Bat_group.update() platform_group.update() Key_group.update() # update score # check if a coin has been collected if pygame.sprite.spritecollide(player, coin_group, True): score += 1 coin_fx.play() draw_text('X ' + str(score), font_score, white, tile_size - 10, 10) # Check if a key has been collected if pygame.sprite.spritecollide(player, Key_group, True): key_collected = True print("collected key") # Draw the key collected status draw_text("Key Collected: {}".format(str(key_collected)), font_score, white, 700, 10) #Draw all the sprite groups Bat_group.draw(screen) platform_group.draw(screen) lava_group.draw(screen) coin_group.draw(screen) exit_group.draw(screen) exitkey_group.draw(screen) Key_group.draw(screen) # Pass both game_over and key_collected to the update method game_over, key_collected = player.update(game_over, key_collected) # if player has died if game_over == -1: if restart_button.draw(): world_data = [] world = reset_level(level) game_over = 0 key_collected = False score -= 8 #Make sure player can't go into negative values with score if score < 0: score = 0 # 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 key_collected = False # Reset state of key else: #Player has won draw_text('YOU WIN!', font, white, (screen_width // 2) - 140, screen_height // 2) draw_text('Your Score [{}]!'.format(score), font, white, (screen_width // 2) - 215, (screen_height // 2) + 70) print("Your Score [{}]!".format(score)) if restart_button.draw(): level = 1 # reset level world_data = [] world = reset_level(level) game_over = 0 score = 0 key_collected = False for event in pygame.event.get(): if event.type == pygame.QUIT: run = False pygame.display.update() pygame.quit()