#-------------------------------------------------------------------------- # Name : Megamans Journey # Standard: US 91896 2.7 # School: Tauranga Boys College # Author: Daniel Han # Date: 29/04/2024 # Python: 3.19.13 #-------------------------------------------------------------------------- #import modules import pygame from pygame.locals import * import pickle from os import path import os #initialize the Pygame library pygame.init() #fps cap clock = pygame.time.Clock() FPS = 60 #DEFINE CONSTANTS: TILE_SIZE = 50 MAX_LEVELS = 3 MAX_GEMS = 2 SCREEN_WIDTH = 1000 SCREEN_HEIGHT = 1000 FONT = pygame.font.SysFont('Bauhaus 93' , 70) FONT_SCORE = pygame.font.SysFont('Bauhaus 93', 30) WHITE = (255, 255, 255) HIGH_SCORE_FILE = "high_score.txt" #define game variables game_over = 0 main_menu = True level = 1 gem_collected = 0 #set up the display screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption('Megamans Journey') #load images moon_img = pygame.image.load('img/moon.png') background_img = pygame.image.load('img/background.png') restart_img = pygame.image.load('img/reset.png') start_img = pygame.image.load('img/play.png') exit_img = pygame.image.load('img/exit.png') restart_img = pygame.transform.scale(restart_img, (170, 75)) #defining draw_text to use later def draw_text(text, font, text_col, x, y): img = font.render(text, True, text_col) screen.blit(img, (x, y)) #function to reset level def reset_level(level): player.reset(100, SCREEN_HEIGHT - 130) spike_group.empty() toxic_group.empty() portal_group.empty() gem_group.empty() # Clear gem_group here #load level data and generate 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) # Add score gem back to the group score_gem = Gem(TILE_SIZE // 2 + 40, TILE_SIZE // 2 + 50) gem_group.add(score_gem) return world # load high score def load_high_score(): if os.path.exists(HIGH_SCORE_FILE): with open(HIGH_SCORE_FILE, 'r') as file: try: return int(file.read().strip()) except ValueError: return 0 return 0 #saving high score def save_high_score(high_score): with open(HIGH_SCORE_FILE, 'w') as file: file.write(str(high_score)) #resetting high score def reset_high_score(): global high_score high_score = 0 save_high_score(high_score) #score game variables: score = 0 high_score = load_high_score() #ALL CLASSES: #button class 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 class Player(): def __init__(self, x, y): self.reset(x ,y) def update(self, game_over): dx = 0 dy = 0 walk_cooldown = 5 global gem_collected #(variable is accessable outside and not local) if game_over == 0: #get keypresses and actions according to those keypresses key = pygame.key.get_pressed() if key[pygame.K_SPACE] and self.jumped == False and self.in_air == False: self.vel_y = -15 self.jumped = True if key[pygame.K_SPACE] == False: 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 #when not moving, no animation if key[pygame.K_LEFT] == False and key[pygame.K_RIGHT] == False: self.counter = 0 self.index = 0 #when both left and right keys are pressed, no animation if key[pygame.K_LEFT] == True and key[pygame.K_RIGHT] == True: 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] #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 #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 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, spike_group, False) or pygame.sprite.spritecollide(self, toxic_group, False): game_over = -1 #check for collision with portal if pygame.sprite.spritecollide(self, portal_group, False): if gem_collected >= MAX_GEMS: game_over = 1 else: game_over = 0 #update player coordinates self.rect.x += dx self.rect.y += dy 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 > 200: self.rect.y -= 5 #draw player onto screen screen.blit(self.image, self.rect) return game_over #defining player resetting: def reset(self, x, y): self.images_right = [] self.images_left = [] self.index = 0 self.counter = 0 for num in range(1, 5): img_right = pygame.image.load(f'img/megaman{num}.png') img_right = pygame.transform.scale(img_right, (50, 80)) img_left = pygame.transform.flip(img_right, True, False) self.images_right.append(img_right) self.images_left.append(img_left) dead_img = pygame.image.load('img/dead.png') self.dead_image = pygame.transform.scale(dead_img, (50, 80)) 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 #world class class World(): def __init__(self, data): self.tile_list = [] #load images tile_img = pygame.image.load('img/tile.png') bluetile_img = pygame.image.load('img/bluetile.png') #tile numbers row_count = 0 for row in data: col_count = 0 for tile in row: #every integer corresponding to a tile image: if tile == 1: img = pygame.transform.scale(tile_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(bluetile_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: spike = Spike(col_count * TILE_SIZE, row_count * TILE_SIZE + 30) spike_group.add(spike) if tile == 4: toxic = Toxic(col_count * TILE_SIZE, row_count * TILE_SIZE + (TILE_SIZE // 2)) toxic_group.add(toxic) if tile == 5: gem = Gem(col_count * TILE_SIZE + 25, row_count * TILE_SIZE + (TILE_SIZE // 2)) gem_group.add(gem) if tile == 6: portal = Portal(col_count * TILE_SIZE, row_count * TILE_SIZE - 25) portal_group.add(portal) col_count += 1 row_count += 1 #defining drawing the tiles def draw(self): for tile in self.tile_list: screen.blit(tile[0], tile[1]) #spike class class Spike(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load('img/spike.png') self.image = pygame.transform.scale(self.image, (35, 35)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y - 15 self.move_direction = 1 self.move_counter = 0 #moving spike horizontally def update(self): self.rect.x += self.move_direction self.move_counter += 1 #check if abs value of move_counter > 50 if abs(self.move_counter) > 50: self.move_direction *= -1 self.move_counter *= -1 #toxic class class Toxic(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/toxic.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 #gem class class Gem(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/gem.png') self.image = pygame.transform.scale(img, (TILE_SIZE // 2, TILE_SIZE // 2)) self.rect = self.image.get_rect() self.rect.center = (x, y) #portal class class Portal(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/portal.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 #defines the player and gives coordinates player = Player(100, SCREEN_HEIGHT - 130) #making groups spike_group = pygame.sprite.Group() toxic_group = pygame.sprite.Group() gem_group = pygame.sprite.Group() portal_group = pygame.sprite.Group() #create gem image for gem counter score_gem = Gem(TILE_SIZE // 2 + 40, TILE_SIZE // 2 + 50) gem_group.add(score_gem) #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) #calling in the world data world = World(world_data) #CREATE BUTTONS: #reset high score button resetscore_button_img = pygame.image.load('img/resetscore.png') resetscore_button_img = pygame.transform.scale(resetscore_button_img, (170, 75)) resetscore_button = Button(SCREEN_WIDTH // 2 - 85, SCREEN_HEIGHT // 2 + 150, resetscore_button_img) #restart game button restart_button = Button(SCREEN_WIDTH // 2 - 85 , SCREEN_HEIGHT // 2 + 70, restart_img) #start button start_button_img = pygame.image.load('img/play.png') start_button_img = pygame.transform.scale(start_button_img, (250, 100)) start_button = Button(SCREEN_WIDTH // 2 - 125, SCREEN_HEIGHT // 2 - 80, start_button_img) #exit button exit_button_img = pygame.image.load('img/exit.png') exit_button_img = pygame.transform.scale(exit_button_img, (290, 110)) exit_button = Button(SCREEN_WIDTH // 2 - 145, SCREEN_HEIGHT // 2.2 + 70, exit_button_img) #MAIN GAME LOOP run = True while run: #fps cap clock.tick(FPS) #background and moon on screen screen.blit(background_img, (0, 0)) screen.blit(moon_img, (100, 100)) #check if in main menu if main_menu == True: #exit button clicked, close the game if exit_button.draw(): run = False #start button clicked, close main menu if start_button.draw(): main_menu = False else: world.draw() if game_over == 0: spike_group.update() #update score #check if a gem has been collected if pygame.sprite.spritecollide(player, gem_group, True): score += 1 if score > high_score: #check if the current score is > than the high score high_score = score #update high score save_high_score(high_score) #store the updated high score in the file. gem_collected += 1 #score text (aka gem #) draw_text('-- ' + str(score), FONT_SCORE, WHITE, TILE_SIZE + 10, 60) #drawing groups on screen spike_group.draw(screen) toxic_group.draw(screen) portal_group.draw(screen) gem_group.draw(screen) #update the player's state and handle game over conditions game_over = player.update(game_over) #putting high score text onto screen high_score_text = FONT_SCORE.render(f"High Score: {high_score}", True, WHITE) screen.blit(high_score_text, (50, 90)) #if player has died if game_over == -1: if restart_button.draw(): world = reset_level(level) game_over = 0 gem_collected = 0 score = 0 # if player enters portal and at least 2 gems are collected if game_over == 1: if level < MAX_LEVELS: level += 1 world_data = [] world = reset_level(level) game_over = 0 gem_collected = 0 else: #if player enters portal and level is > 3 (aka last level), you win! draw_text('YOU WIN!', FONT, WHITE, (SCREEN_WIDTH // 2) - 140, SCREEN_HEIGHT // 2) if restart_button.draw(): level = 1 world_data = [] world = reset_level(level) game_over = 0 gem_collected = 0 score = 0 #resetting high score if reset high score button clicked if game_over == -1: if resetscore_button.draw(): reset_high_score() elif game_over == MAX_LEVELS: # reset game_over status if gems are not sufficient game_over = 0 #when game is quit for event in pygame.event.get(): if event.type == pygame.QUIT: run = False pygame.display.update() pygame.quit()