#-------------------------------------------------------------------------- # Name : Platformer # Standard: US 91896 2.7 # School: Tauranga Boys College # Author: James Darby # Date: 29/04/2024 # Python: 3.19.13 #-------------------------------------------------------------------------- # Importing modules import pygame from pygame.locals import * from pygame import mixer import pickle from os import path # Initializing pygame and the pygame mixer pygame.mixer.pre_init(44100, -16, 2, 512) mixer.init() pygame.init() # Setting up pygame clock and fps value clock = pygame.time.Clock() FPS = 60 # Setting up the screen dimensions SCREEN_WIDTH = 1000 SCREEN_HEIGHT = 1000 # Creating the screen screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption('Platformer') # Define fonts for the game font = pygame.font.SysFont('Bauhaus 93', 70) font_score = pygame.font.SysFont('Bauhaus 93', 30) # Define game variables TILE_SIZE = 50 game_over = 0 main_menu = True level = 1 MAX_LEVELS = 3 score = 0 endscore = 0 # Define colors WHITE = (255, 255, 255) RED = (255, 0, 0) # Load images sun_img = pygame.image.load('img/sun.png') bg_img = pygame.image.load('img/bg.png') 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') # Load sounds pygame.mixer.music.load('img/music.wav') pygame.mixer.music.play(-1, 0.0, 5000) coin_fx = pygame.mixer.Sound('img/coin.wav') coin_fx.set_volume(0.5) jump_fx = pygame.mixer.Sound('img/jump.wav') jump_fx.set_volume(0.5) game_over_fx = pygame.mixer.Sound('img/game_over.wav') game_over_fx.set_volume(0.5) # Function to draw text on the screen def draw_text(text, font, text_col, x, y): img = font.render(text, True, text_col) screen.blit(img, (x, y)) # Function to reset the level def reset_level(level): player.reset(100, SCREEN_HEIGHT - 130) blob_group.empty() lava_group.empty() exit_group.empty() platform_group.empty() key_group.empty() # Load level data and create the 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 # Button class for handling 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 if mouse is over the button and clicked 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 on screen screen.blit(self.image, self.rect) return action # Player class for handling the player character class Player(): def __init__(self,x,y): self.reset(x, y) def update(self, game_over): dx = 0 dy = 0 col_thresh = 20 if game_over == 0: # Get keypresses for movement key = pygame.key.get_pressed() if key[pygame.K_SPACE] and self.jumped == False and self.in_air == False: jump_fx.play() self.vel_y = -15 self.jumped = True if key[pygame.K_SPACE] == False: self.jumped = False if key[pygame.K_LEFT]: dx -= 5 self.direction = -1 if key[pygame.K_RIGHT]: dx += 5 self.direction = 1 if key[pygame.K_LEFT] == False and key[pygame.K_RIGHT] == False: self.direction = 0 # Handle animation based on movement direction if self.in_air: if self.direction == 1: self.image = self.jump_image_right elif self.direction == -1: self.image = self.jump_image_left else: self.image = self.jump_image_right # default to right if no direction else: if self.direction == 1: self.image = self.image_right elif self.direction == -1: self.image = self.image_left else: self.image = self.image_right # default to right if no direction # Add gravity self.vel_y += 1 if self.vel_y > 10: self.vel_y = 10 dy += self.vel_y # Check for collisions with the world tiles self.in_air = True for tile in world.tile_list: # x-axis collision if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): dx = 0 # y-axis collision if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): # Check underside of a block (jumping) if self.vel_y < 0: dy = tile[1].bottom - self.rect.top self.vel_y = 0 # Check top side of a block (falling) elif self.vel_y >= 0: dy = tile[1].top - self.rect.bottom self.vel_y = 0 self.in_air = False # Check for collisions with enemies if pygame.sprite.spritecollide(self, blob_group, False): game_over = -1 game_over_fx.play() # Check for collisions with lava if pygame.sprite.spritecollide(self, lava_group, False): game_over = -1 game_over_fx.play() # Check for collisions with the exit if pygame.sprite.spritecollide(self, exit_group, False): if self.key_collected: game_over = 1 # Check for collisions with keys if pygame.sprite.spritecollide(self, key_group, True): self.key_collected = True # Check for collisions with platforms for platform in platform_group: # Collision in x-axis if platform.rect.colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): dx = 0 # Collision in y-axis if platform.rect.colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): # Checking collision underneath platform if abs((self.rect.top + dy) - platform.rect.bottom) < col_thresh: self.vel_y = 0 dy = platform.rect.bottom - self.rect.top # Checking collision 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 # Player moves sideways with platform if platform.move_x != 0: self.rect.x += platform.move_direction # Update player coordinates self.rect.x += dx self.rect.y += dy elif game_over == -1: self.image = self.dead_image # Set player to the center of the screen self.rect.x = (SCREEN_WIDTH // 4) - (self.width // 2) draw_text('You Died!', font, RED, (SCREEN_WIDTH // 2) - 135, (SCREEN_HEIGHT // 2) - 140) if self.rect.y > 200: self.rect.y -= 5 # Draw player on screen screen.blit(self.image, self.rect) return game_over def reset(self, x, y): self.image_right = pygame.image.load('img/guy1.png') self.image_right = pygame.transform.scale(self.image_right, (40, 80)) self.image_left = pygame.transform.flip(self.image_right, True, False) self.jump_image_right = pygame.image.load('img/guy2.png') self.jump_image_right = pygame.transform.scale(self.jump_image_right, (40, 80)) self.jump_image_left = pygame.transform.flip(self.jump_image_right, True, False) self.dead_image = pygame.image.load('img/ghost.png') self.image = self.image_right 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 self.key_collected = False # World class for handling the game world class World(): def __init__(self, data): self.tile_list = [] # Load images for the tiles dirt_img = pygame.image.load('img/dirt.png') grass_img = pygame.image.load('img/grass.png') row_count = 0 for row in data: col_count = 0 for tile in row: if tile == 1: img = pygame.transform.scale(dirt_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(grass_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: blob = Enemy(col_count * TILE_SIZE, row_count * TILE_SIZE + 15) blob_group.add(blob) 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: key = Key(col_count * TILE_SIZE + (TILE_SIZE // 2), row_count * TILE_SIZE + (TILE_SIZE // 2)) key_group.add(key) col_count += 1 row_count += 1 def draw(self): for tile in self.tile_list: screen.blit(tile[0], tile[1]) # Enemy class for handling enemies class Enemy(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load('img/blob.png') self.image = pygame.transform.scale(self.image, (TILE_SIZE // 1.4, TILE_SIZE // 1.4)) 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) > 50: self.move_direction *= -1 self.move_counter *= -1 # Platform class for handling moving platforms class Platform(pygame.sprite.Sprite): def __init__(self, x, y, move_x, move_y): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load('img/platform.png') self.image = pygame.transform.scale(self.image, (TILE_SIZE, TILE_SIZE // 2)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.move_direction = 1 self.move_counter = 0 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 class for handling lava tiles class Lava(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/lava.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 # Coin class for handling collectible coins class Coin(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/coin.png') self.image = pygame.transform.scale(img, (TILE_SIZE // 2, TILE_SIZE // 2)) self.rect = self.image.get_rect() self.rect.center = (x, y) # Exit class for handling the exit portal 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 # Key class for handling collectible keys class Key(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/key.png') self.image = pygame.transform.scale(img, (TILE_SIZE // 2, TILE_SIZE // 2)) self.rect = self.image.get_rect() self.rect.center = (x, y) # Create sprite groups blob_group = pygame.sprite.Group() lava_group = pygame.sprite.Group() coin_group = pygame.sprite.Group() exit_group = pygame.sprite.Group() platform_group = pygame.sprite.Group() key_group = pygame.sprite.Group() # Create player instance player = Player(100, SCREEN_HEIGHT - 130) # Load level data and create world instance 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) # Main game loop run = True while run: # Fps cap clock.tick(FPS) # Draw the background and the sun images screen.blit(bg_img, (0, 0)) screen.blit(sun_img, (100, 100)) if main_menu: # Check if the exit button is pressed if exit_button.draw(): run = False # Check if the start button is pressed if start_button.draw(): main_menu = False else: # Draw the game world world.draw() if game_over == 0: # Update enemy and platform positions blob_group.update() platform_group.update() coin_group.update() key_group.update() # Check for coin collisions and update the score if pygame.sprite.spritecollide(player, coin_group, True): coin_fx.play() score += 1 draw_text('Score: ' + str(score), font_score, WHITE, 20, 8) # Draw all the game elements blob_group.draw(screen) lava_group.draw(screen) coin_group.draw(screen) exit_group.draw(screen) platform_group.draw(screen) key_group.draw(screen) # Update player state and check for game over game_over = player.update(game_over) if game_over == -1: # If game over, display restart button if restart_button.draw(): # Reset the level and game variables world_data = [] world = reset_level(level) game_over = 0 score = endscore if game_over == 1: # Advance to the next level level += 1 if level <= MAX_LEVELS: # Reset the level and game variables world_data = [] world = reset_level(level) game_over = 0 endscore = score else: # Display win message and restart button draw_text('YOU WIN!', font, RED, (SCREEN_WIDTH // 2) - 140, (SCREEN_HEIGHT // 2) - 140) draw_text(f'Final Score: {score}' , font, RED, (SCREEN_WIDTH // 2) - 160, (SCREEN_HEIGHT // 2) - 50) if restart_button.draw(): level = 1 world_data = [] world = reset_level(level) game_over = 0 score = 0 # Check for events like window close for event in pygame.event.get(): if event.type == pygame.QUIT: run = False # Update the display pygame.display.update() # Quit pygame pygame.quit()