''' ---------------------------------------- Project : Platformer Standard : 91896 School : Tauranga Boys College Author : Hayden De Rohan Date : Apirl 28th 2025 Python : 3.11.9 ---------------------------------------- Fix wall collision ''' import pygame import math from pygame import mixer import os import random import csv import button from debug import debug from support import import_folder # Initilise mixer.init() pygame.init() SCREEN_W = 800 SCREEN_H = int(SCREEN_W * 0.8) screen = pygame.display.set_mode((SCREEN_W, SCREEN_H)) pygame.display.set_caption('Platformer') #set framerate clock = pygame.time.Clock() FPS = 60 #define game variables GRAVITY = 0.6 SCROLL_THRESH = 100 ROWS = 16 COLS = 150 TILE_SIZE = SCREEN_H // ROWS TILE_TYPES = 21 MAX_LEVELS = 3 screen_scroll = 0 bg_scroll = 0 level = 0 start_game = False start_intro = False level_complete = False score = 0 high_score = 0 music_started = False #define Player action variables moving_left = False moving_right = False img_list = [] for x in range(TILE_TYPES): img = pygame.image.load(f'Assets/Tiles/{x}.png').convert_alpha() img = pygame.transform.scale(img, (TILE_SIZE, TILE_SIZE)) img_list.append(img) #fire Projectile_img = pygame.image.load('Assets/Enemy2/attack/fire.png').convert_alpha() #background bg_img = pygame.image.load('Assets/bg.png').convert_alpha() #buttons start_img = pygame.image.load('Assets/Buttons/start_img.png').convert_alpha() exit_img = pygame.image.load('Assets/Buttons/exit_img.png').convert_alpha() restart_img = pygame.image.load('Assets/Buttons/restart_img.png').convert_alpha() interact_img = pygame.image.load('Assets/Buttons/interactE.png').convert_alpha() interact_img = pygame.transform.scale( interact_img, (int(interact_img.get_width() * 0.4), int(interact_img.get_height() * 0.4)) ) #Sounds shoot = pygame.mixer.Sound("Assets/Audio/shoot.mp3") coin_get = pygame.mixer.Sound("Assets/Audio/coin.mp3") coin_get.set_volume(0.3) hurt = pygame.mixer.Sound("Assets/Audio/hurt.mp3") jump = pygame.mixer.Sound("Assets/Audio/jump.mp3") jump.set_volume(0.5) key_get = pygame.mixer.Sound("Assets/Audio/key.mp3") #Music bg_music = pygame.mixer.Sound("Assets/Audio/bg_music.mp3") #define colours BG = (144, 201, 120) RED = (255, 0, 0) WHITE = (255, 255, 255) GREEN = (0, 255, 0) BLACK = (0, 0, 0) PINK = (235, 65, 54) #define font font = pygame.font.SysFont('Futura', 30) # Load score from text file def load_score(): try: with open("score.txt", "r") as file: return int(file.read()) except FileNotFoundError: return 0 # Default value if file doesn't exist # Save score to text file def save_score(score): with open("score.txt", "w") as file: file.write(str(score)) # text renderer def draw_text(text, font, text_col, x, y): img = font.render(text, True, text_col) screen.blit(img, (x, y)) # draw background def draw_bg(): screen.fill(BG) width = bg_img.get_width() for x in range(5): screen.blit(bg_img, ((x * width) - bg_scroll * 0.5, 0)) # Update and draw groups def game_setup(): global music_started if not music_started: pygame.mixer.music.load("Assets/Audio/bg_music.mp3") pygame.mixer.music.set_volume(0.4) # Set volume for music stream pygame.mixer.music.play(loops=-1) # Loops indefinitely music_started = True Projectile_group.update() grenade_group.update() explosion_group.update() item_box_group.update() decoration_group.update() water_group.update() exit_group.update() key_group.update() coin_group.update() sign_group.update() Projectile_group.draw(screen) grenade_group.draw(screen) explosion_group.draw(screen) item_box_group.draw(screen) decoration_group.draw(screen) water_group.draw(screen) exit_group.draw(screen) key_group.draw(screen) coin_group.draw(screen) sign_group.draw(screen) #function to reset level def reset_level(): enemy_group.empty() Projectile_group.empty() decoration_group.empty() water_group.empty() exit_group.empty() coin_group.empty() key_group.empty() sign_group.empty() Player.picked_up_key = False #create empty tile list data = [] for row in range(ROWS): r = [-1] * COLS data.append(r) return data #Character class, Player 1 and Enemy 1 class Character(pygame.sprite.Sprite): def __init__(self, char_type, x, y, scale, speed): pygame.sprite.Sprite.__init__(self) self.alive = True self.char_type = char_type self.speed = speed self.shoot_cooldown = 0 self.health = 100 self.max_health = self.health self.direction = pygame.math.Vector2(0, 0) self.vel_y = 0 self.jump = False self.flip = False self.action = 0 self.update_time = pygame.time.get_ticks() self.in_air = False self.on_ground = False self.scale = scale self.picked_up_key = False self.import_character_assets() # Animations self.frame_index = 0 self.animation_speed = 0.15 self.image = self.animations['idle'][self.frame_index] self.rect = self.image.get_rect() self.rect.midbottom = (x, y) self.width = self.image.get_width() * scale self.height = self.image.get_height() * scale # Status self.facing_right = True self.status = 'idle' # AI-specific variables self.move_counter = 0 self.vision = pygame.Rect(0, 0, 225, 40) self.idling = False self.idling_counter = 0 self.player_found = False self.Can_damage_player = False # Damage cooldown variables self.damage_cooldown = 0 self.damage_cooldown_time = 1000 self.damage_amount = 20 self.damage_timer = 0 # Timer to control damage animation duration self.damage_duration = 750 self.is_damaged = False # Flag to track if player is in damaged state # Red flash variables self.red_flash_active = False self.red_flash_timer = 0 self.red_flash_duration = 200 def import_character_assets(self): Path = (f'Assets/{self.char_type}/') self.animations = {'idle': [], 'walk': [], 'jump': [], 'death': [], 'fall': [], 'damaged': []} for animation in self.animations.keys(): full_path = Path + animation loaded_frames = import_folder(full_path) scaled_frames = [] for image in loaded_frames: width = int(image.get_width() * self.scale) height = int(image.get_height() * self.scale) scaled_image = pygame.transform.scale(image, (width, height)) scaled_frames.append(scaled_image) self.animations[animation] = scaled_frames def get_status(self): if self.direction.y < 0: self.status = 'jump' elif self.direction.y > 0: self.status = 'fall' else: if self.direction.x != 0: self.status = 'walk' else: self.status = 'idle' def animate(self): if self.status == "damaged": self.animation_speed = 0.2 elif self.status == "idle": self.animation_speed = 0.05 elif self.status == "fall": self.animation_speed = 0.45 else: self.animation_speed = 0.15 animation = self.animations[self.status] self.frame_index += self.animation_speed if self.frame_index >= len(animation): if self.status == "damaged" and not self.is_damaged: self.frame_index = 0 self.get_status() # Revert to idle/walk/jump after damage else: self.frame_index = 0 image = animation[int(self.frame_index)] if self.facing_right: self.image = image else: flip_img = pygame.transform.flip(image, True, False) self.image = flip_img old_midbottom = self.rect.midbottom self.rect = self.image.get_rect(midbottom=old_midbottom) def move(self, moving_left, moving_right): screen_scroll = 0 dx = 0 dy = 0 # Calculate horizontal movement if moving_left: dx = -self.speed self.facing_right = False self.flip = True self.direction.x = -1 elif moving_right: dx = self.speed self.facing_right = True self.flip = False self.direction.x = 1 else: self.direction.x = 0 # Handle jumping if self.jump and not self.in_air: jump.play() self.vel_y = -13 self.on_ground = False self.in_air = True self.jump = False #Apply gravity self.vel_y += GRAVITY if self.vel_y > 10: self.vel_y = 10 dy += self.vel_y # Update direction for status if self.on_ground: self.direction.y = 0 elif self.vel_y > 0: self.direction.y = 1 elif self.vel_y < 0: self.direction.y = -1 # Collision detection with obstacles for tile in world.obstacle_list: # Check horizontal collision if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.rect.width, self.rect.height): if self.direction.x < 0: self.rect.left = tile[1].right if self.direction.x > 0: self.rect.right = tile[1].left if self.char_type == 'enemy': self.direction.x *= -1 self.move_counter = 0 # Check vertical collision if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.rect.width, self.rect.height): if self.vel_y < 0: # Moving up (jumping) dy = tile[1].bottom - self.rect.top elif self.vel_y >= 0: # Falling dy = tile[1].top - self.rect.bottom self.in_air = False self.on_ground = True # Handle screen scrolling for player if self.char_type == 'Player': if self.rect.right > SCREEN_W - SCROLL_THRESH and bg_scroll < (world.level_length * TILE_SIZE) - SCREEN_W: screen_scroll = -dx self.rect.x -= dx # Counteract movement to keep player in place elif self.rect.left < SCROLL_THRESH and bg_scroll > 0: screen_scroll = -dx self.rect.x -= dx # Counteract movement to keep player in place # Coin collision collided_coins = pygame.sprite.spritecollide(self, coin_group, True) if collided_coins and self.char_type == 'Player': global score coin_get.play() score += 1 # Update position self.rect.x += dx self.rect.y += dy # Update status self.get_status() debug(f"dx: {dx}, dy: {dy}, rect.x: {self.rect.x}, rect.y: {self.rect.y}, direction.y{self.direction.y}, direction.x{self.direction.x}") return screen_scroll def ai(self): # Update damage cooldown if self.damage_cooldown > 0: self.damage_cooldown -= pygame.time.get_ticks() - self.update_time if self.damage_cooldown < 0: self.damage_cooldown = 0 # Debugging vision rect #pygame.draw.rect(screen, RED, self.vision) # Idle function if self.alive and Player.alive: if self.idling == False and random.randint(1, 100) == 1: self.update_action(0) self.idling = True self.idling_counter = 50 # Detecting Player function if self.vision.colliderect(Player.rect): self.player_found = True self.update_action(1) self.speed = 6 # Chase player if self.direction.x == 1: ai_moving_right = True ai_moving_left = False self.move(ai_moving_left, ai_moving_right) self.vision.center = (self.rect.centerx + 115 * self.direction.x, self.rect.centery) else: ai_moving_left = True ai_moving_right = False self.move(ai_moving_left, ai_moving_right) self.vision.center = (self.rect.centerx + 115 * self.direction.x, self.rect.centery) # No player found else: self.speed = 3 self.player_found = False if self.idling == False: if self.direction.x == 1: ai_moving_right = True else: ai_moving_right = False ai_moving_left = not ai_moving_right self.move(ai_moving_left, ai_moving_right) self.update_action(1) self.move_counter += 1 self.vision.center = (self.rect.centerx + 115 * self.direction.x, self.rect.centery) if self.move_counter > TILE_SIZE: self.direction.x *= -1 self.move_counter *= -1 else: self.idling_counter -= 1 if self.idling_counter <= 0: self.idling = False # If touching player if self.rect.colliderect(Player.rect): if self.damage_cooldown <= 0: # Can deal damage Player.take_damage(self.damage_amount) # Use take_damage self.damage_cooldown = self.damage_cooldown_time # Reset cooldown if Player.health <= 0: self.check_alive() self.rect.x += screen_scroll self.update_time = pygame.time.get_ticks() # Update time for next frame def update_action(self, new_action): if new_action != self.action: self.action = new_action self.frame_index = 0 self.update_time = pygame.time.get_ticks() def check_alive(self): if self.health <= 0: self.health = 0 self.speed = 0 self.alive = False self.status = 'death' self.update_action(3) def draw(self): screen.blit(self.image, self.rect) if self.char_type == 'Player': pygame.draw.rect(screen, (255,0,0), self.rect, 1) def take_damage(self, amount): #Handle taking damage and trigger damage animation and red flash if self.alive and not self.is_damaged: # Only take damage if not already damaged self.health -= amount self.is_damaged = True self.damage_timer = self.damage_duration self.status = 'damaged' self.update_action(5) # Assuming 'damaged' is action 5 hurt.play() # Trigger red flash self.red_flash_active = True self.red_flash_timer = self.red_flash_duration def get_status(self): #Update the player's status, respecting the damage animation. if self.is_damaged: self.status = 'damaged' else: if self.direction.y > 0: self.status = 'jump' elif self.direction.y < 0: self.status = 'fall' else: if self.direction.x != 0: self.status = 'walk' else: self.status = 'idle' def update(self): #Update the player, including damage and red flash timers if self.is_damaged: self.damage_timer -= pygame.time.get_ticks() - self.update_time if self.damage_timer <= 0: self.is_damaged = False # End damage animation if self.red_flash_active: self.red_flash_timer -= pygame.time.get_ticks() - self.update_time if self.red_flash_timer <= 0: self.red_flash_active = False # End red flash self.animate() self.check_alive() self.update_time = pygame.time.get_ticks() # Update time after all checks # Enemy 2 (The one that shoots), Add new collisions when it's fixed on the Character class PLEASE!!!!!!!! class Enemy2(pygame.sprite.Sprite): def __init__(self, char_type, x, y, scale, speed): pygame.sprite.Sprite.__init__(self) self.alive = True self.char_type = char_type self.speed = speed self.health = 100 self.max_health = self.health self.direction = pygame.math.Vector2(0, 0) self.vel_y = 0 self.jump = False self.flip = False self.action = 0 self.update_time = pygame.time.get_ticks() self.in_air = False self.on_ground = True self.scale = scale self.picked_up_key = False self.import_character_assets() # Animations self.frame_index = 0 self.animation_speed = 0.15 self.image = self.animations['idle'][self.frame_index] self.rect = self.image.get_rect() self.rect.midbottom = (x, y) self.width = self.image.get_width() * scale self.height = self.image.get_height() * scale # Status self.facing_right = True self.status = 'idle' # AI-specific variables self.move_counter = 0 self.vision = pygame.Rect(0, 0, 500, 300) self.idling = False self.idling_counter = 0 self.player_found = False # Shooting attributes self.shoot_cooldown = 0 self.shoot_cooldown_time = 1500 self.projectile_speed = 0.5 self.last_shot_time = 0 def import_character_assets(self): Path = (f'Assets/{self.char_type}/') self.animations = {'idle': [], 'walk': [], 'jump': [], 'death': [], 'fall': [], 'damaged': []} for animation in self.animations.keys(): full_path = Path + animation loaded_frames = import_folder(full_path) scaled_frames = [] for image in loaded_frames: width = int(image.get_width() * self.scale) height = int(image.get_height() * self.scale) scaled_image = pygame.transform.scale(image, (width, height)) scaled_frames.append(scaled_image) self.animations[animation] = scaled_frames def get_status(self): if self.direction.y < 0: self.status = 'jump' elif self.direction.y > 0: self.status = 'fall' else: if self.direction.x != 0: self.status = 'walk' else: self.status = 'idle' def animate(self): if self.status == "damaged": self.animation_speed = 0.2 elif self.status == "idle": self.animation_speed = 0.05 elif self.status == "fall": self.animation_speed = 0.45 else: self.animation_speed = 0.15 animation = self.animations[self.status] self.frame_index += self.animation_speed if self.frame_index >= len(animation): if self.status == "damaged": self.frame_index = 0 self.get_status() # Revert to idle/walk/jump after damage else: self.frame_index = 0 image = animation[int(self.frame_index)] if self.facing_right: self.image = image else: flip_img = pygame.transform.flip(image, True, False) self.image = flip_img old_midbottom = self.rect.midbottom self.rect = self.image.get_rect(midbottom=old_midbottom) def move(self, moving_left, moving_right): screen_scroll = 0 dx = 0 dy = 0 # Calculate horizontal movement if moving_left: dx = -self.speed self.facing_right = False self.flip = True self.direction.x = -1 elif moving_right: dx = self.speed self.facing_right = True self.flip = False self.direction.x = 1 else: self.direction.x = 0 dx = 0 # Handle jumping if self.jump and not self.in_air: jump.play() self.vel_y = -13 self.on_ground = False self.in_air = True self.jump = False # Apply gravity self.vel_y += GRAVITY if self.vel_y > 10: self.vel_y = 10 dy += self.vel_y # Collision detection with obstacles for tile in world.obstacle_list: # Check horizontal collision if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.rect.width, self.rect.height): dx = 0 # Stop horizontal movement completely if self.direction.x < 0: self.rect.left = tile[1].right elif self.direction.x > 0: self.rect.right = tile[1].left if self.char_type == 'enemy': self.direction.x *= -1 self.move_counter = 0 # Check vertical collision if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.rect.width, self.rect.height): if self.vel_y < 0: # Moving up (jumping) dy = tile[1].bottom - self.rect.top self.vel_y = 0 # Stop upward movement elif self.vel_y >= 0: # Falling dy = tile[1].top - self.rect.bottom self.vel_y = 0 # Stop downward movement self.in_air = False self.on_ground = True # Handle screen scrolling for player if self.char_type == 'Player': if self.rect.right > SCREEN_W - SCROLL_THRESH and bg_scroll < (world.level_length * TILE_SIZE) - SCREEN_W: screen_scroll = -dx self.rect.x -= dx # Counteract movement to keep player in place elif self.rect.left < SCROLL_THRESH and bg_scroll > 0: screen_scroll = -dx self.rect.x -= dx # Counteract movement to keep player in place # Coin collision collided_coins = pygame.sprite.spritecollide(self, coin_group, True) if collided_coins and self.char_type == 'Player': global score coin_get.play() score += 1 # Update position self.rect.x += dx self.rect.y += dy # Ensure character doesn't fall through the floor if self.rect.bottom > SCREEN_H: self.rect.bottom = SCREEN_H self.vel_y = 0 self.in_air = False self.on_ground = True # Update direction for status if self.on_ground: self.direction.y = 0 elif self.vel_y > 0: self.direction.y = 1 elif self.vel_y < 0: self.direction.y = -1 # Update status self.get_status() debug(f"dx: {dx}, dy: {dy}, rect.x: {self.rect.x}, rect.y: {self.rect.y}, direction.y: {self.direction.y}, direction.x: {self.direction.x}") return screen_scroll def ai(self): # Debugging vision rect #pygame.draw.rect(screen, RED, self.vision) # Idle function if self.alive and Player.alive: if self.idling == False and random.randint(1, 1000) == 1: self.update_action(0) self.idling = True self.idling_counter = random.randint(20, 100) # Detecting Player function if self.vision.colliderect(Player.rect): self.player_found = True self.update_action(1) # Shoot player current_time = pygame.time.get_ticks() if current_time - self.last_shot_time >= self.shoot_cooldown_time: # Calculate direction to player player_pos = pygame.math.Vector2(Player.rect.center) enemy_pos = pygame.math.Vector2(self.rect.center) direction_to_player = (player_pos - enemy_pos).normalize() # Create projectile projectile = Projectile( self.rect.centerx, self.rect.centery, direction_to_player ) Projectile_group.add(projectile) self.last_shot_time = current_time # Update last shot time # No player found else: self.player_found = False if self.idling == False: if self.direction.x == 1: ai_moving_right = True else: ai_moving_right = False ai_moving_left = not ai_moving_right self.move(ai_moving_left, ai_moving_right) self.update_action(1) self.move_counter += 1 self.vision.center = (self.rect.centerx + 215 * self.direction.x, self.rect.centery) if self.move_counter > TILE_SIZE: self.direction.x *= -1 self.move_counter *= -1 else: self.idling_counter -= 1 if self.idling_counter <= 0: self.idling = False self.rect.x += screen_scroll self.update_time = pygame.time.get_ticks() # Update time for next frame def update_action(self, new_action): if new_action != self.action: self.action = new_action self.frame_index = 0 self.update_time = pygame.time.get_ticks() def check_alive(self): if self.health <= 0: self.health = 0 self.speed = 0 self.alive = False self.status = 'death' self.update_action(3) def draw(self): screen.blit(self.image, self.rect) def get_status(self): """Update the player's status, respecting the damage animation.""" if self.direction.y < 0: self.status = 'jump' elif self.direction.y > 0: self.status = 'fall' else: if self.direction.x != 0: self.status = 'walk' else: self.status = 'idle' def update(self): self.animate() self.check_alive() # Update shoot cooldown if self.shoot_cooldown > 0: self.shoot_cooldown -= pygame.time.get_ticks() - self.update_time if self.shoot_cooldown < 0: self.shoot_cooldown = 0 self.update_time = pygame.time.get_ticks() class HealthBar(): def __init__(self, x, y, health, max_health): self.x = x self.y = y self.health = health self.max_health = max_health def draw(self, health): # Update with new health self.health = health # Calculate health ratio ratio = self.health / self.max_health # Scale # by 1.5 border_width = int(154 * 1.5) border_height = int(24 * 1.5) bar_width = int(150 * 1.5) bar_height = int(20 * 1.5) border_offset = int(2 * 1.5) # Draw rectangles with scaled dimensions pygame.draw.rect(screen, BLACK, (self.x - border_offset, self.y - border_offset, border_width, border_height)) pygame.draw.rect(screen, RED, (self.x, self.y, bar_width, bar_height)) pygame.draw.rect(screen, GREEN, (self.x, self.y, bar_width * ratio, bar_height)) #World class class World(): def __init__(self): self.obstacle_list = [] # In the World class, inside the process_data method def process_data(self, data): self.level_length = len(data[0]) Player = None # Initialize Player as None health_bar = None # Initialize health_bar as None # Iterate through each value in level data file for y, row in enumerate(data): for x, tile in enumerate(row): if tile >= 0: img = img_list[tile] img_rect = img.get_rect() img_rect.x = x * TILE_SIZE img_rect.y = y * TILE_SIZE tile_data = (img, img_rect) if tile >= 0 and tile <= 8: self.obstacle_list.append(tile_data) elif tile >= 9 and tile <= 10: water = Water(img, x * TILE_SIZE, y * TILE_SIZE) water_group.add(water) elif tile >= 11 and tile <= 13: decoration = Decoration(img, x * TILE_SIZE, y * TILE_SIZE) decoration_group.add(decoration) elif tile == 14: # Create Player and HealthBar Player = Character('Player', x * TILE_SIZE, y * TILE_SIZE + TILE_SIZE, 1.3, 4) health_bar = HealthBar(15, 15, Player.health, Player.max_health) # Create HealthBar elif tile == 15: # Create enemies enemy = Character('Enemy', x * TILE_SIZE, y * TILE_SIZE + TILE_SIZE, 1.3, 3) enemy_group.add(enemy) elif tile == 16: # Create exit exit = Exit(img, x * TILE_SIZE, y * TILE_SIZE) exit_group.add(exit) elif tile == 17: # Create enemy that shoots enemy = Enemy2('Enemy2', x * TILE_SIZE, y * TILE_SIZE + TILE_SIZE, 0.7, 1) enemy_group.add(enemy) elif tile == 18: # Create Key key = Key(img, x * TILE_SIZE, y * TILE_SIZE) key_group.add(key) elif tile == 19: # Create Coins coin = Coin(img, x * TILE_SIZE, y * TILE_SIZE) coin_group.add(coin) elif tile == 20: # Information Sign sign = Sign(img, x * TILE_SIZE, y * TILE_SIZE) sign_group.add(sign) return Player, health_bar def draw(self): for tile in self.obstacle_list: tile[1][0] += screen_scroll screen.blit(tile[0], tile[1]) # Draw hitbox for tiles pygame.draw.rect(screen, (0,255,0), tile[1], 2) # Blue outline, 2px thick class Key(pygame.sprite.Sprite): def __init__(self, img, x, y): pygame.sprite.Sprite.__init__(self) scale_factor = 0.8 new_width = int(img.get_width() * scale_factor * 0.6) new_height = int(img.get_height() * scale_factor) self.image = pygame.transform.scale(img, (new_width, new_height)) self.rect = self.image.get_rect() self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height())) #Animation self.base_y = self.rect.y self.bob_timer = 0 self.bob_amplitude = 5 self.bob_speed = 0.06 def update(self): #Animation self.bob_timer += self.bob_speed y_offset = math.sin(self.bob_timer) * self.bob_amplitude self.rect.y = self.base_y + y_offset self.rect.x += screen_scroll class Decoration(pygame.sprite.Sprite): def __init__(self, img, x, y): pygame.sprite.Sprite.__init__(self) self.image = img self.rect = self.image.get_rect() self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height())) def update(self): self.rect.x += screen_scroll class Water(pygame.sprite.Sprite): def __init__(self, img, x, y): pygame.sprite.Sprite.__init__(self) self.image = img self.rect = self.image.get_rect() self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height())) def update(self): self.rect.x += screen_scroll class Exit(pygame.sprite.Sprite): def __init__(self, img, x, y): pygame.sprite.Sprite.__init__(self) self.image = img self.rect = self.image.get_rect() self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height())) def update(self): self.rect.x += screen_scroll class Sign(pygame.sprite.Sprite): def __init__(self, img, x, y): pygame.sprite.Sprite.__init__(self) self.image = img self.rect = self.image.get_rect() self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height())) def update(self): self.rect.x += screen_scroll class Coin(pygame.sprite.Sprite): def __init__(self, img, x, y): pygame.sprite.Sprite.__init__(self) scale_factor = 0.6 new_width = int(img.get_width() * scale_factor) new_height = int(img.get_height() * scale_factor) self.image = pygame.transform.scale(img, (new_width, new_height)) self.rect = self.image.get_rect() self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height())) #Animation self.base_y = self.rect.y self.bob_timer = 0 self.bob_amplitude = 5 self.bob_speed = 0.08 def update(self): #Animation self.bob_timer += self.bob_speed y_offset = math.sin(self.bob_timer) * self.bob_amplitude self.rect.y = self.base_y + y_offset self.rect.x += screen_scroll #Projectile class class Projectile(pygame.sprite.Sprite): def __init__(self, x, y, direction): pygame.sprite.Sprite.__init__(self) self.speed = 5 # Adjust as needed self.original_image = Projectile_img # Store original image self.direction = direction # Vector2 direction # Calculate rotation angle angle = math.degrees(math.atan2(-direction.y, direction.x)) + 90 # Negative y due to Pygame's y-axis # Rotate the image self.image = pygame.transform.rotate(self.original_image, angle) self.rect = self.image.get_rect() self.rect.center = (x, y) #Play sound shoot.play() def update(self): # Move projectile self.rect.x += (self.direction.x * self.speed) + screen_scroll self.rect.y += (self.direction.y * self.speed) # Support vertical movement # Check if projectile has gone off screen if self.rect.right < 0 or self.rect.left > SCREEN_W or self.rect.bottom < 0 or self.rect.top > SCREEN_H: self.kill() # Check for collision with level for tile in world.obstacle_list: if tile[1].colliderect(self.rect): self.kill() # Check collision with characters if pygame.sprite.spritecollide(Player, Projectile_group, False): if Player.alive: Player.take_damage(10) self.kill() #Screen fade class ScreenFade(): def __init__(self, direction, colour, speed): self.direction = direction self.colour = colour self.speed = speed self.fade_counter = 0 def fade(self): fade_complete = False self.fade_counter += self.speed if self.direction == 1:#whole screen fade pygame.draw.rect(screen, self.colour, (0 - self.fade_counter, 0, SCREEN_W // 2, SCREEN_H)) pygame.draw.rect(screen, self.colour, (SCREEN_W // 2 + self.fade_counter, 0, SCREEN_W, SCREEN_H)) pygame.draw.rect(screen, self.colour, (0, 0 - self.fade_counter, SCREEN_W, SCREEN_H // 2)) pygame.draw.rect(screen, self.colour, (0, SCREEN_H // 2 +self.fade_counter, SCREEN_W, SCREEN_H)) if self.direction == 2:#vertical screen fade down pygame.draw.rect(screen, self.colour, (0, 0, SCREEN_W, 0 + self.fade_counter)) if self.fade_counter >= SCREEN_W: fade_complete = True return fade_complete #create screen fades intro_fade = ScreenFade(1, BLACK, 8) death_fade = ScreenFade(2, PINK, 12) #create buttons start_button = button.Button( SCREEN_W // 2 - 75, SCREEN_H // 2 - 50, start_img, 1 ) exit_button = button.Button( SCREEN_W // 2 - 75, SCREEN_H // 2 + 50, exit_img, 1) restart_button = button.Button( SCREEN_W // 2 - 75, SCREEN_H // 2 - 50, restart_img, 1) #create sprite groups enemy_group = pygame.sprite.Group() Projectile_group = pygame.sprite.Group() grenade_group = pygame.sprite.Group() explosion_group = pygame.sprite.Group() item_box_group = pygame.sprite.Group() decoration_group = pygame.sprite.Group() water_group = pygame.sprite.Group() exit_group = pygame.sprite.Group() key_group = pygame.sprite.Group() coin_group = pygame.sprite.Group() sign_group = pygame.sprite.Group() #create empty tile list world_data = [] for row in range(ROWS): r = [-1] * COLS world_data.append(r) #load in level data and create world with open(f'level{level}_data.csv', newline='') as csvfile: reader = csv.reader(csvfile, delimiter=',') for x, row in enumerate(reader): for y, tile in enumerate(row): world_data[x][y] = int(tile) world = World() Player, health_bar = world.process_data(world_data) # Initialize high score at the start high_score = load_score() run = True while run: clock.tick(FPS) if start_game == False: # Draw menu screen.blit(bg_img, (0, 0)) # Add buttons if start_button.draw(screen): start_game = True start_intro = True if exit_button.draw(screen): run = False else: # Update background draw_bg() #Health bar health_bar.draw(Player.health) # Draw world map world.draw() # Draw player Player.update() Player.draw() for enemy in enemy_group: enemy.ai() enemy.update() enemy.draw() # Update and draw groups + music game_setup() # Display high score and level as text draw_text(f"Level: {level}", font, WHITE, 15, 50) # Display level at (10, 50) draw_text(f"High Score: {high_score}", font, WHITE, 15, 80) # Display high score at (10, 80) draw_text(f"Score: {score}", font, WHITE, 15, 110) # Display current score at (10, 110) ''' # Debug menus ''' debug(f"In air? {Player.in_air}", 150, 15) debug(f"On ground? {Player.on_ground}", 190, 15) # Show intro if start_intro == True: if intro_fade.fade(): start_intro = False intro_fade.fade_counter = 0 # Update Player actions if Player.alive: if Player.in_air: Player.update_action(2) # 2: jump elif moving_left or moving_right: Player.update_action(1) # 1: walk else: Player.update_action(0) # 0: idle screen_scroll = Player.move(moving_left, moving_right) bg_scroll -= screen_scroll # Check if score exceeds high_score if score > high_score: high_score = score save_score(high_score) # Save new high score to file # Complete level if level_complete: start_intro = True level += 1 bg_scroll = 0 world_data = reset_level() level_complete = False if level <= MAX_LEVELS: with open(f'level{level}_data.csv', newline='') as csvfile: reader = csv.reader(csvfile, delimiter=',') for x, row in enumerate(reader): for y, tile in enumerate(row): world_data[x][y] = int(tile) world = World() Player, health_bar = world.process_data(world_data) # Check for collision with keys collided_keys = pygame.sprite.spritecollide(Player, key_group, False) if collided_keys: key = collided_keys[0] interact_x = key.rect.centerx - interact_img.get_width() // 2 interact_y = key.rect.top - interact_img.get_height() - 10 screen.blit(interact_img, (interact_x, interact_y)) # Check for collision with exit collide_exit = pygame.sprite.spritecollide(Player, exit_group, False) if collide_exit and Player.picked_up_key: exit = collide_exit[0] interact_x = exit.rect.centerx - interact_img.get_width() // 2 interact_y = exit.rect.top - interact_img.get_height() - 10 screen.blit(interact_img, (interact_x, interact_y)) else: screen_scroll = 0 if death_fade.fade(): if restart_button.draw(screen): death_fade.fade_counter = 0 start_intro = True bg_scroll = 0 score = 0 world_data = reset_level() with open(f'level0_data.csv', newline='') as csvfile: reader = csv.reader(csvfile, delimiter=',') for x, row in enumerate(reader): for y, tile in enumerate(row): world_data[x][y] = int(tile) world = World() level = 0 Player, health_bar = world.process_data(world_data) # Draw red flash when player takes damage if Player.red_flash_active: red_surface = pygame.Surface((SCREEN_W, SCREEN_H), pygame.SRCALPHA) red_surface.fill((255, 0, 0)) red_surface.set_alpha(155) screen.blit(red_surface, (0, 0)) # Pygame events for event in pygame.event.get(): if event.type == pygame.QUIT: run = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_a: moving_left = True if event.key == pygame.K_d: moving_right = True if event.key == pygame.K_w and Player.alive: Player.jump = True if event.key == pygame.K_ESCAPE: run = False if event.key == pygame.K_e and Player.alive: collided_keys = pygame.sprite.spritecollide(Player, key_group, True) if collided_keys: print("Key picked up!") Player.picked_up_key = True key_get.play() if Player.picked_up_key: collide_exit = pygame.sprite.spritecollide(Player, exit_group, True) if collide_exit: print("Exit") level_complete = True if event.type == pygame.KEYUP: if event.key == pygame.K_a: moving_left = False if event.key == pygame.K_d: moving_right = False pygame.display.update() pygame.quit()