""" --------------------------------------------- Project: Dungeon Run Standard: School: Tauranga Boys College Author: Aidan Plummer Date: May 2025 Python: 3.11.9 --------------------------------------------- """ # 1:54:10 import pygame import sys from time import sleep from pytmx.util_pygame import load_pygame class Tile(pygame.sprite.Sprite): # Sprite class to handle tiles def __init__(self,pos,surf,groups, fov, alpha=None): super().__init__(groups) new_size = (int(surf.get_width() * fov), int(surf.get_height() * fov)) self.image = pygame.transform.scale(surf, new_size) new_pos = (int(pos[0] * fov), int(pos[1] * fov)) self.rect = self.image.get_rect(topleft = new_pos) if alpha is not None: self.image.set_alpha(alpha) class Player(pygame.sprite.Sprite): def __init__(self, pos, groups, speed, collision_group, debug=False): super().__init__(groups) self.debug = debug raw_image = pygame.image.load("assets/BlueWizard/2BlueWizardIdle/Chara - BlueIdle00000.png") self.original_image = pygame.transform.scale(raw_image, (200, 200)) # Save original image self.image = self.original_image self.rect = self.image.get_rect(center=pos) # set image rect self.rect = pygame.Rect(0, 0, 60, 80) # Changes the hitbox size self.rect.center = pos self.image_rect = self.image.get_rect(midbottom=self.rect.midbottom) self.image_offset_y = 65 # Changes the y offset of the image (not the collision hitbox) self.image_rect.y += self.image_offset_y self.speed = speed self.velocity_y = 0 self.gravity = 1.5 self.jump_strength = 25 self.on_ground = False self.facing_right = True self.state = 'idle' self.collision_group = collision_group def update_image(self): if self.facing_right: self.image = self.original_image else: self.image = pygame.transform.flip(self.original_image, True, False) self.idle_frames = [pygame.image.load(f"assets/BlueWizard/2BlueWizardIdle/Chara - BlueIdle0000{i}.png") for i in range(5)] keys = pygame.key.get_pressed() if not (keys[pygame.K_a] or keys[pygame.K_LEFT] or keys[pygame.K_d] or keys[pygame.K_RIGHT]): self.play_idle_animation() def play_idle_animation(self): self.state = 'idle' def update(self): # Scroll camera if player is past screen boundaries if self.rect.centerx - CAMERA_OFFSET.x < LEFT_SCROLL_BOUNDARY and CAMERA_OFFSET.x >= tmx_data.width: CAMERA_OFFSET.x = self.rect.centerx - LEFT_SCROLL_BOUNDARY elif self.rect.centerx - CAMERA_OFFSET.x > RIGHT_SCROLL_BOUNDARY and CAMERA_OFFSET.x+screen_width <= tmx_data.width*(tmx_data.tilewidth*fov)-10: CAMERA_OFFSET.x = self.rect.centerx - RIGHT_SCROLL_BOUNDARY keys = pygame.key.get_pressed() if keys[pygame.K_a] or keys[pygame.K_LEFT]: self.rect.x -= self.speed # Checks for collision on the left side collided_tiles = pygame.sprite.spritecollide(self, self.collision_group, False) for tile in collided_tiles: if self.rect.colliderect(tile.rect): self.rect.left = tile.rect.right break self.facing_right = False if keys[pygame.K_d] or keys[pygame.K_RIGHT]: self.rect.x += self.speed # Checks for collision on the right side collided_tiles = pygame.sprite.spritecollide(self, self.collision_group, False) for tile in collided_tiles: if self.rect.colliderect(tile.rect): self.rect.right = tile.rect.left break self.facing_right = True # Handles jumping if self.on_ground and (keys[pygame.K_SPACE] or keys[pygame.K_w] or keys[pygame.K_UP]): self.velocity_y = -self.jump_strength self.on_ground = False self.velocity_y += self.gravity self.rect.y += self.velocity_y collided_tiles = pygame.sprite.spritecollide(self, self.collision_group, False) if self.debug: print(f"Player Y: {self.rect.y}, Player X: {self.rect.x}, VelY: {self.velocity_y}, Collisions: {len(collided_tiles)}") if collided_tiles: # Detect when player touches layers that are set in collided_tiles sprite group for tile in collided_tiles: if self.velocity_y > 0: self.rect.bottom = tile.rect.top self.velocity_y = 0 self.on_ground = True break elif self.velocity_y < 0: self.rect.top = tile.rect.bottom self.velocity_y = 0 else: self.on_ground = False self.image_rect.midbottom = self.rect.midbottom self.image_rect.y += self.image_offset_y self.update_image() if self.debug == True: pygame.draw.rect(display, (255,255,255), self.rect, 2) # Visualizes hitbox def MainMenu(): # Sets all the things needed for main menu, mainly the visual stuff global tmx_data, fov, text, text_rect, textbg, textbg_rect, play_text, play_text_rect # Local Variables white = (255,255,255) black = (0,0,0) # Game title and play button texts font = pygame.font.Font('Trajan Pro Bold.ttf', 64) text = font.render("CaveQuest", True, white) text_rect = text.get_rect() text_rect.center = (screen_width / 2, screen_height / 3) font = pygame.font.Font('Trajan Pro Bold.ttf', 64) # Drop shadow for text textbg = font.render("CaveQuest", True, black) textbg_rect = textbg.get_rect() textbg_rect.center = (screen_width / 2-4, screen_height / 3+4) font = pygame.font.Font('Trajan Pro Bold.ttf', 32) play_text = font.render("Play", True, white) play_text_rect = play_text.get_rect() play_text_rect.center = (screen_width / 2, screen_height / 2) fov = .25 tmx_data = load_pygame('mainmenu.tmx') LoadMap(tmx_data=tmx_data) # Loads the main menu background def GameOver(): global game_over game_over = True pygame.mixer.music.stop() print("Game Over") def LoadMap(tmx_data): for obj in tmx_data.objects: # Finds objects (a special type of tile in tiled) and renders them. pos = obj.x, obj.y if obj.image: Tile(pos = pos, surf = obj.image, groups = sprite_group, fov=fov) # Cycle through all layers for layer in tmx_data.visible_layers: if hasattr(layer,'data'): for x,y,surf in layer.tiles(): pos = x * 256, y * 256 if layer.name == "MainLayer" or layer.name == "MenuLayer": # Normal Render alpha=None Tile(pos = pos, surf = surf, groups = (collision_group, main_group), fov=fov, alpha=alpha) if layer.name == "BackgroundLevel": # Checks if tile is part of this layer and passes arguments based on that, alpha = transparency alpha=128 Tile(pos = pos, surf = surf, groups = background_group, fov=fov, alpha=alpha) def Play(): global player, tmx_data, fov tmx_data = load_pygame('Cavern_1.tmx') # Gets tiled map LoadMap(tmx_data=tmx_data) pygame.mixer.music.fadeout = 1 pygame.mixer.music.stop() fov=0.25 player = Player(pos=(500, 200), groups=sprite_group, speed=8, collision_group=collision_group, debug=False) # Player starting position and arguments # START OF RUNNING CODE pygame.init() clock = pygame.time.Clock() # Essential Variables screen_width = 350*3 screen_height = 250*3 LEFT_SCROLL_BOUNDARY = screen_width // 3 RIGHT_SCROLL_BOUNDARY = screen_width * 2 // 3 CAMERA_OFFSET = pygame.Vector2(0, 0) game_over = False main_menu = True player = None tmx_data = None fov = 0.25 # How much the camera is zoomed out, smaller = zoom in, larger = zoom out, 0.25 default fps = 60 display = pygame.display.set_mode((screen_width,screen_height)) pygame.display.set_caption("CaveQuest") sprite_group = pygame.sprite.Group() foreground_group = pygame.sprite.Group() main_group = pygame.sprite.Group() background_group = pygame.sprite.Group() collision_group = pygame.sprite.Group() # Audio Variables music_mainmenu = pygame.mixer.music.load('assets/Audio/MainMenu.mp3') # Background Images mushroomcavebg1 = pygame.image.load("assets/background\Mushroom_Cave_L1.png") mushroomcavebg1 = pygame.transform.scale(mushroomcavebg1, (screen_width,screen_height)) MainMenu() while main_menu == True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: # Left mouse button if play_text_rect.collidepoint(event.pos): main_menu = False sprite_group.empty() foreground_group.empty() main_group.empty() background_group.empty() collision_group.empty() Play() display.blit(mushroomcavebg1, (0, 0)) display.blit(textbg, textbg_rect) display.blit(text, text_rect) display.blit(play_text, play_text_rect) for sprite in background_group: display.blit(sprite.image, sprite.rect.topleft) for sprite in main_group: display.blit(sprite.image, sprite.rect.topleft) for sprite in foreground_group: display.blit(sprite.image, sprite.rect.topleft) pygame.display.update() if not pygame.mixer.music.get_busy(): pygame.mixer.music.play(loops=-1, fade_ms=100) clock.tick(fps) while True and not main_menu == True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if game_over == False: sprite_group.update() display.blit(mushroomcavebg1, (0, 0)) background_group.update() foreground_group.update() for sprite in background_group: display.blit(sprite.image, sprite.rect.topleft - CAMERA_OFFSET) display.blit(player.image, player.image_rect.topleft - CAMERA_OFFSET) # Draws player on screen for sprite in main_group: display.blit(sprite.image, sprite.rect.topleft - CAMERA_OFFSET) for sprite in foreground_group: display.blit(sprite.image, sprite.rect.topleft - CAMERA_OFFSET) if player.rect.y >= screen_height: GameOver() pygame.display.update() clock.tick(fps)