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 dimensions screen_width = 800 screen_height = 800 # Create a game window screen = pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption('Knight Journey') #define font font = pygame.font.SysFont('Times New Roman', 70) font_score = pygame.font.SysFont('Times New Roman', 26) #define game variables tile_size = 40 game_over = 0 main_menu = True level = 1 max_levels = 3 score = 0 death_count = 0 #define colours black = (0, 0, 0) white = (255, 255, 255) # Initialize timer variables start_time = 0 current_time = 0 timer_running = False #load images sun_img = pygame.image.load('img/sun.png') bg_img = pygame.image.load('img/sky.png') bg_img = pygame.transform.scale(bg_img, (screen_width, screen_height)) 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.3) death_fx = pygame.mixer.Sound('img/death.wav') death_fx.set_volume(0.5) def draw_text(text, font, text_col, x, y): img = font.render(text, True, text_col) screen.blit(img, (x + 7, y - 5)) #function to reset level def reset_level(level): player.reset(100, screen_height - 146) slime_group.empty() spikes_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 Player(): def __init__(self, x, y): self.reset(x, y) global death_count def update(self, game_over): global death_count dx = 0 dy = 0 walk_cooldown = 7 if game_over == 0: #get keypresses keys = pygame.key.get_pressed() #handle jumping if keys[pygame.K_UP] and self.jumped == False and self.in_air == False: jump_fx.play() self.vel_y = -15 self.jumped = True if not keys[pygame.K_UP]: self.jumped = False #handle left and right movement if keys[pygame.K_LEFT] and not keys[pygame.K_RIGHT]: dx -= 4 self.counter += 1 self.direction = -1 elif keys[pygame.K_RIGHT] and not keys[pygame.K_LEFT]: dx += 4 self.counter += 1 self.direction = 1 elif keys[pygame.K_LEFT] and keys[pygame.K_RIGHT]: self.index = 0 #handle animation if self.counter > walk_cooldown: self.counter = 0 self.index += 1 if self.index >= len(self.images_right): self.index = 0 elif not any((keys[pygame.K_LEFT], keys[pygame.K_RIGHT])): self.index = 0 #update 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] # 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 eg: jumping if self.vel_y < 0: dy = tile[1].bottom - self.rect.top self.vel_y = 0 #check if above the ground eg: 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, slime_group, False): game_over = -1 death_count += 1 death_fx.play() # Check for collision with spikes if pygame.sprite.spritecollide(self, spikes_group, False): game_over = -1 death_count += 1 death_fx.play() # Check for collision with exit if pygame.sprite.spritecollide(self, exit_group, False): if score > 0: game_over = 1 # Update player coordinates self.rect.x += dx self.rect.y += dy elif game_over == -1: # Removed 'GAME OVER' text display from here # Fall down when game over self.vel_y += 1 if self.vel_y > 10: self.vel_y = 10 dy += self.vel_y # Animate death frames if self.counter < len(self.dead_images) * walk_cooldown: self.index = self.counter // walk_cooldown self.image = self.dead_images[self.index] self.counter += 1 else: self.image = self.dead_images[-1] # Only handle downward collisions during death for tile in world.tile_list: if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): if self.vel_y >= 0: dy = tile[1].top - self.rect.bottom self.vel_y = 0 else: dy = 0 # Prevent teleportation issue self.rect.y += dy # Draw player onto screen screen.blit(self.image, self.rect) return game_over def reset(self, x, y): self.images_right = [] self.images_left = [] self.dead_images = [] self.index = 0 self.counter = 0 # Load player images for num in range(1, 6): img_right = pygame.image.load(f'img/guy{num}.png') img_right = pygame.transform.scale(img_right, (40, 66)) img_left = pygame.transform.flip(img_right, True, False) self.images_right.append(img_right) self.images_left.append(img_left) # Load dead images for num in range(1, 9): img_dead = pygame.image.load(f'img/dead{num}.png') img_dead = pygame.transform.scale(img_dead, (40, 66)) self.dead_images.append(img_dead) # Set initial image and rectangle self.image = self.images_right[self.index] self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y # Set player dimensions self.width = self.image.get_width() self.height = self.image.get_height() # Set initial velocity and jump status self.vel_y = 0 self.jumped = False self.direction = 0 self.in_air = True #define a class called World. class World(): #initialize the World object. def __init__(self, data): #create an empty list to store tiles. self.tile_list = [] #load images dirt_img = pygame.image.load('img/dirt.png') grass_img = pygame.image.load('img/grass.png') #set initial row count to 0. row_count = 0 # Loop through each row in the data. for row in data: # Set initial column count to 0. col_count = 0 # Loop through each tile in the current row. for tile in row: # Check if the tile value is 1. if tile == 1: # Scale the image to a specific size. img = pygame.transform.scale(dirt_img, (tile_size, tile_size)) # Create a rectangle for the tile's position. img_rect = img.get_rect() # Set the x and y positions of the image rectangle. img_rect.x = col_count * tile_size img_rect.y = row_count * tile_size # Create a tuple containing the image and its rectangle, and add it to the tile list. tile = (img, img_rect) self.tile_list.append(tile) if tile == 2: # Scale the dirt image to a specific size. img = pygame.transform.scale(grass_img, (tile_size, tile_size)) # Create a rectangle for the tile's position. img_rect = img.get_rect() # Set the x and y positions of the image rectangle. img_rect.x = col_count * tile_size img_rect.y = row_count * tile_size # Create a tuple containing the image and its rectangle, and add it to the tile list. tile = (img, img_rect) self.tile_list.append(tile) if tile == 3: slime = Enemy(col_count * tile_size, row_count * tile_size + 15) slime_group.add(slime) if tile == 6: spikes = Spikes(col_count * tile_size, row_count * tile_size + (tile_size // 2)) spikes_group.add(spikes) 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) # Increment the column count. col_count += 1 # Increment the row count. row_count += 1 # Define a method to draw the tiles on the screen. def draw(self): # Loop through each tile in the tile list. for tile in self.tile_list: # Blit (draw) the image of the tile onto the screen at the position specified by its rectangle. screen.blit(tile[0], tile[1]) class Enemy(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load('img/slime.png') self.image = pygame.transform.scale(self.image, (36, 25)) # Resize the slime 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 self.move_counter > 50: self.move_direction *= -1 self.move_counter *= -1 class Spikes(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/spikes.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 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) 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 - 146) slime_group = pygame.sprite.Group() spikes_group = pygame.sprite.Group() coin_group = pygame.sprite.Group() exit_group = pygame.sprite.Group() #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 + 50, screen_height // 2, exit_img) # Main game loop run = True while run: clock.tick(fps) screen.blit(bg_img, (0, 0)) screen.blit(sun_img, (725, 25)) if main_menu == True: if exit_button.draw(): run = False if start_button.draw(): main_menu = False else: world.draw() if game_over == 0: slime_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(': ' + str(score), font_score, black, tile_size - 10, 10) slime_group.draw(screen) spikes_group.draw(screen) coin_group.draw(screen) exit_group.draw(screen) #create icon/display coin score_coin = Coin(tile_size // 2, tile_size // 2) coin_group.add(score_coin) 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 score = 0 #if player has beaten level if game_over == 1: #reset and next level level += 1 score -= 1 if level <= max_levels: #reset level world_data = [] world = reset_level(level) game_over = 0 else: draw_text(f'Deaths: {death_count}', font_score, black, tile_size - 10, 40) draw_text('YOU WON!', font, white, (screen_width // 2) - 140, screen_height // 2) #restart game if restart_button.draw(): level = 1 world_data = [] world = reset_level(level) game_over = 0 score = 0 death_count = 0 for event in pygame.event.get(): if event.type == QUIT: run = False pygame.display.update() pygame.quit()