import pygame, sys from pygame.locals import * from pygame import mixer import pickle from os import path # basic game setup pygame.mixer.pre_init(44100, -16, 2, 512) mixer.init() pygame.init() clock = pygame.time.Clock() fps = 60 screen_width,screen_height = 1000,1000 screen = pygame.display.set_mode((screen_width,screen_height)) pygame.display.set_caption('Platformer') # define font font = pygame.font.SysFont('Bauhaus 93', 70) font_score = pygame.font.SysFont('Bauhaus 93', 30) #define timer variables timer = 5 start_time = pygame.time.get_ticks # define game variables tile_size = 25 game_over = 0 main_menu = True level = 1 death_checker = 0 max_levels = 5 # define score variables score = 0 score2 = 0 current_score = score current_score2 = score2 high_score = 0 # define colours white = (255, 255, 255) blue = (0, 0, 255) red = (255, 0, 0) # load images bg_img = pygame.image.load('img/sky.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') pitfall_img = pygame.image.load('img/pitfall2.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) # create tiles and line spacings def draw_grid(): for line in range(0, 20): pygame.draw.line(screen, (255, 255, 255), (0, line * tile_size), (screen_width, line * tile_size)) pygame.draw.line(screen, (255, 255, 255), (line * tile_size, 0), (line * tile_size, screen_height)) # setup for drawing text onto 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 level def reset_level(level): player.reset(1, 100, screen_height - 100) player2.reset(2, 100, screen_height - 100) blob_group.empty() platform_group.empty() lava_group.empty() exit_group.empty() pitfall_group.empty() crossbow_group.empty() arrow_group.empty() crossbow_flip_group.empty() key_group.empty() key_group2.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) world.update(world_data) return world, world_data # adding of high score into level def load_high_score(): global high_score try: with open("high_score.txt", "r") as file: data = file.read().strip() if data: high_score = int(data) else: # set high score if file empty high_score = 0 # set high score if file not found except FileNotFoundError: high_score = 0 # saving function for new high scores def save_high_score(): with open("high_score.txt", "w") as file: file.write(str(high_score)) # finding function for new high scores def update_high_score(): global high_score if score > high_score or score2 > high_score: if score > score2: high_score = score elif score2 > score: high_score = score2 save_high_score() # in game button creation 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 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: self.clicked = True action = True if pygame.mouse.get_pressed()[0] == 0: self.clicked = False # draw button screen.blit(self.image, self.rect) return action # playable character function class Player(): def __init__(self, char_type, x, y): self.reset(char_type,x,y) def update(self, game_over): dx = 0 dy = 0 walk_cooldown = 7 col_thresh = 20 if game_over == 0 and self.dead == False: # get player one key presses key = pygame.key.get_pressed() if self.char_type == 1 and self.dead == False: if key[pygame.K_UP] and self.jumped == False and self.in_air == False: jump_fx.play() self.vel_y = -14 self.jumped = True if key[pygame.K_UP] == 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 if key[pygame.K_LEFT] == False and key[pygame.K_RIGHT] == False: 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] if pygame.sprite.spritecollide(self, key_group, True): self.key_1_collected = True # get player two key presses if self.char_type == 2 and self.dead == False: if key[pygame.K_w] and self.jumped == False and self.in_air == False: jump_fx.play() self.vel_y = -14 self.jumped = True if key[pygame.K_w] == False: self.jumped = False if key[pygame.K_a]: dx -= 5 self.counter += 1 self.direction = -1 if key[pygame.K_d]: dx += 5 self.counter += 1 self.direction = 1 if key[pygame.K_a] == False and key[pygame.K_d] == False: 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] if pygame.sprite.spritecollide(self, key_group2, True): self.key_2_collected = True # 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 pitfall trap for pit in world.pit_list: #check for collision in x direction if pit[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): dx = 0 #check for collision in y direction if pit[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 = pit[1].bottom - self.rect.top self.vel_y = 0 # check if above the ground i.e. falling elif self.vel_y >= 0: dy = pit[1].top - self.rect.bottom self.vel_y = 0 self.in_air = False # check for collision with enemies if pygame.sprite.spritecollide(self, blob_group, False): game_over = -1 self.dead = True game_over_fx.play() # check for collision with Lava if pygame.sprite.spritecollide(self, lava_group, False): game_over = -1 self.dead = True game_over_fx.play() # check for collision with Exit if pygame.sprite.spritecollide(self, exit_group, False): if self.char_type == 1 and self.key_1_collected == True: self.dead = True game_over = 1 if self.char_type == 2 and self.key_2_collected == True: self.dead = True game_over = 1 # check for collision with arrows if pygame.sprite.spritecollide(self, arrow_group, False): self.dead = True game_over = -1 # check for collision with bottom of map if self.rect.y > screen_height: game_over = -1 self.dead = True # check for collision with platforms for platform in platform_group: #collision in the x direction if platform.rect.colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): dx = 0 # collision in the y direction if platform.rect.colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): #check if below platform if abs((self.rect.top + dy) - platform.rect.bottom) < col_thresh: self.vel_y = 0 dy = platform.rect.bottom - self.rect.top # check if above the 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 #move sideways with the platform if platform.move_x != 0: self.rect.x += platform.move_direction # update player coordinates self.rect.x += dx self.rect.y += dy # handle death animations elif self.dead == True: if level <= max_levels: self.image = self.dead_image if death_checker == 2: draw_text('GAME OVER!', font, blue, (screen_width // 2) - 200, screen_width // 2) if self.rect.y > 200: self.rect.y -= 5 # draw player onto screen screen.blit(self.image, self.rect) return game_over def reset(self, char_type, x, y): # player vairables self.images_right = [] self.images_left = [] self.index = 0 self.counter = 0 self.char_type = char_type self.dead = False for num in range(1, 5): img_right = pygame.image.load(f'img/guy{self.char_type}{num}.png') img_right = self.image = pygame.transform.scale(img_right, (20, 40)) img_left = pygame.transform.flip(img_right, True, False) self.images_right.append(img_right) self.images_left.append(img_left) self.dead_image = pygame.image.load(f'img/ghost{self.char_type}.png') 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 self.key_1_collected = False self.key_2_collected = False class World(): def __init__(self, data): # load tile lists self.tile_list = [] self.pit_list = [] #load images rock_img = pygame.image.load('img/rock.png') surface_img = pygame.image.load('img/surface.png') # load tiles from level data row_count = 0 for row in data: col_count = 0 for tile in row: if tile == 1: #rock img = pygame.transform.scale(rock_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: #surface img = pygame.transform.scale(surface_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 blob = Enemy(col_count * tile_size, row_count * tile_size + 5) blob_group.add(blob) if tile == 4: #platform x platform = Platform(col_count * tile_size, row_count * tile_size, 1, 0) platform_group.add(platform) if tile == 5: #platform y platform = Platform(col_count * tile_size, row_count * tile_size, 0, 1) platform_group.add(platform) if tile == 6: #lava lava = Lava(col_count * tile_size, row_count * tile_size + (tile_size // 2)) lava_group.add(lava) if tile == 7: #exit exit = Exit(col_count * tile_size, row_count * tile_size - (tile_size // 2)) exit_group.add(exit) if tile == 8: #pitfall pitfall = Pitfall(col_count * tile_size, row_count * tile_size) pitfall_group.add(pitfall) pit_img = pygame.transform.scale(pitfall_img, (tile_size, tile_size)) pit_img_rect = pit_img.get_rect() pit_img_rect.x = col_count * tile_size pit_img_rect.y = row_count * tile_size pit = (pit_img, pit_img_rect) self.pit_list.append(pit) self.pit_list.clear() if tile == 9: #crossbow left crossbow = Crossbow(col_count * tile_size, row_count * tile_size) crossbow_group.add(crossbow) if tile == 10: # crossbow right crossbow = Crossbow(col_count * tile_size, row_count * tile_size) crossbow_flip_group.add(crossbow) if tile == 11: # player 1 key key = Key(col_count * tile_size, row_count * tile_size, 1) key_group.add(key) if tile == 12: # player 2 key key = Key(col_count * tile_size, row_count * tile_size, 2) key_group2.add(key) col_count += 1 row_count += 1 def draw(self): for tile in self.tile_list: # drawing of tiles screen.blit(tile[0], tile[1]) for pit in self.pit_list: # drawing of pitfall screen.blit(pit[0], pit[1]) def update(self, data): self.pit_list = [] # pitfall trap animation (good) row_count = 0 for row in data: col_count = 0 for tile in row: for pitfall in pitfall_group: if tile == 8: if pitfall.ticks == 1: pit_img = pygame.transform.scale(pitfall_img, (tile_size, tile_size)) pit_img_rect = pit_img.get_rect() pit_img_rect.x = col_count * tile_size pit_img_rect.y = row_count * tile_size pit = (pit_img, pit_img_rect) self.pit_list.append(pit) elif pitfall.ticks == 2: pit_img = pygame.transform.scale(pitfall_img, (tile_size, tile_size)) pit_img_rect = pit_img.get_rect() pit = (pit_img, pit_img_rect) for pit in self.pit_list: self.pit_list.remove(pit) col_count += 1 row_count += 1 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, (25, 20)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.move_direction = 1 self.move_counter = 0 #blob movement def update(self): self.rect.x += self.move_direction self.move_counter += 1 if abs(self.move_counter) > 25: self.move_direction *= -1 self.move_counter *= -1 class Platform(pygame.sprite.Sprite): def __init__(self, x, y, move_x, move_y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/platform.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 self.move_counter = 0 self.move_direction = 1 self.move_x = move_x self.move_y = move_y # platform movement 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) > 25: self.move_direction *= -1 self.move_counter *= -1 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 class Exit(pygame.sprite.Sprite): def __init__(self, x, y,): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/exit_full_locked.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 def update(self): # all the exit possibities alongside the keys if player.key_1_collected == False and player2.key_2_collected == False: self.image = pygame.image.load('img/exit_full_locked.png') elif player.key_1_collected == True and player2.key_2_collected == False: self.image = pygame.image.load('img/exit_blue_locked.png') elif player.key_1_collected == False and player2.key_2_collected == True: self.image = pygame.image.load('img/exit_skin_locked.png') elif player.key_1_collected == True and player2.key_2_collected == True: self.image = pygame.image.load('img/exit.png') self.image = pygame.transform.scale(self.image, (tile_size, int(tile_size * 1.5))) class Pitfall(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.images = [pygame.transform.scale(pygame.image.load('img/pitfall1.png'), (tile_size, tile_size)), pygame.transform.scale(pygame.image.load('img/pitfall2.png'), (tile_size, tile_size))] self.image_index = 0 self.image = self.images[self.image_index] self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.count = 0 self.ticks = 1 self.frame_count = 0 self.collisions = False # pitfall animation timer def update(self, cycle_speed): self.frame_count += 1 if self.frame_count % cycle_speed == 0: self.image_index = (self.image_index + 1) % len(self.images) self.image = self.images[self.image_index] self.collisions = True self.ticks += 1 if self.ticks == 3: self.ticks = 1 screen.blit(self.image, self.rect) class Crossbow(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.images = [pygame.transform.scale(pygame.image.load(f'img/crossbow{n}.png'), (tile_size, tile_size)) for n in range(1, 5)] self.image_index = 0 self.image = self.images[self.image_index] self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.ticks = 1 self.frame_count = 0 self.collisions = False self.flip = False def update(self, cycle_speed, flip_flag=False): self.frame_count += 1 # used to see which way the crossbow is facing if flip_flag: self.flip = True self.image = pygame.transform.flip(self.images[self.image_index], True, False) else: self.image = self.images[self.image_index] #crossbow animation if self.frame_count % cycle_speed == 0: if self.flip: # flipping of the image self.image = pygame.transform.flip(self.images[self.image_index], True, False) else: self.image = self.images[self.image_index] self.image_index = (self.image_index + 1) % len(self.images) self.collisions = True self.ticks += 1 # arrow creation timer if self.ticks == 5: self.ticks = 1 arrow_group.add(Crossbow.create_arrow(1,self.rect.x, self.rect.y, flip_flag)) screen.blit(self.image, self.rect) # arrow creation def create_arrow(self, x , y, flip): return Arrow(x, y, flip) class Arrow(pygame.sprite.Sprite): def __init__(self, x, y, flip): pygame.sprite.Sprite.__init__(self) self.flip = flip # arrow direction if self.flip == True: img = pygame.image.load('img/arrow_flip.png') else: img = pygame.image.load('img/arrow.png') self.image = pygame.transform.scale(img, (tile_size, tile_size)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y # arrow movement def update(self): if self.flip == True: self.rect.x += 5 else: self.rect.x -= 5 # arrow destruction for tile_data in world.tile_list: tile_rect = tile_data[1] if self.rect.colliderect(tile_rect): # Destroy the arrow when it collides with a tile self.kill() break for pit_data in world.pit_list: pit_rect = pit_data[1] if self.rect.colliderect(pit_rect): # Destroy the arrow when it collides with a tile self.kill() break class Key(pygame.sprite.Sprite): def __init__(self, x, y, key_type): pygame.sprite.Sprite.__init__(self) self.key_type = key_type if key_type == 1: img = pygame.image.load('img/key_1.png') if key_type == 2: img = pygame.image.load('img/key_2.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 # Players player = Player(1, 100, screen_height - 100) player2 = Player(2, 100, screen_height - 100) # Groups arrow_group = pygame.sprite.Group() blob_group = pygame.sprite.Group() platform_group = pygame.sprite.Group() lava_group = pygame.sprite.Group() exit_group = pygame.sprite.Group() pitfall_group = pygame.sprite.Group() crossbow_group = pygame.sprite.Group() crossbow_flip_group = pygame.sprite.Group() key_group = pygame.sprite.Group() key_group2 = pygame.sprite.Group() # High score load_high_score() # 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 + 150, screen_height // 2, exit_img) run = True while run: clock.tick(fps) screen.blit(bg_img, (0,0)) # button drawing and title screen if main_menu == True: if exit_button.draw(): run = False if start_button.draw(): main_menu = False else: world.draw() if game_over == 0 or 1: # all the updating functions blob_group.update() platform_group.update() pitfall_group.update(100) arrow_group.update() exit_group.update() world.update(world_data) crossbow_group.update(15, False) crossbow_flip_group.update(15, True) # update score #check if a level has been finished if level <= max_levels: if pygame.sprite.spritecollide(player, exit_group, False) and player.dead == False and player.key_1_collected == True: score += 1 coin_fx.play() if pygame.sprite.spritecollide(player2, exit_group, False) and player2.dead == False and player2.key_2_collected == True: score2 += 1 coin_fx.play() # text on screen draw_text('P1 ' + str(score), font_score, white, tile_size - 20, 1) draw_text('P2 ' + str(score2), font_score, blue, tile_size - 20, 40) draw_text('High Score ' + str(high_score), font_score, red, screen_width - 185, 1) # drawing images onto screen blob_group.draw(screen) platform_group.draw(screen) lava_group.draw(screen) exit_group.draw(screen) pitfall_group.draw(screen) crossbow_group.draw(screen) arrow_group.draw(screen) crossbow_flip_group.draw(screen) key_group.draw(screen) key_group2.draw(screen) # game over detection game_over = player.update(game_over) game_over = player2.update(game_over) # one player has died if game_over == -1: death_checker += 1 game_over = 0 # both players have died if death_checker == 2: # reset level if restart_button.draw(): world_data = [] pit_data = [] world, world_data = reset_level(level) game_over = 0 death_checker = 0 world.update(world_data) # if player has completed the level if game_over == 1: # reset game and got to next level update_high_score() level += 1 death_checker = 0 if level <= max_levels: # reset level world_data = [] pit_data = [] world, world_data = reset_level(level) game_over = 0 else: draw_text('YOU WIN!', font, blue, (screen_width // 2) - 140, screen_height // 2) # restart games if restart_button.draw(): level = 1 # reset level world_data = [] pit_data = [] world, world_data = reset_level(level) game_over = 0 score = 0 # closing game for event in pygame.event.get(): if event.type == pygame.QUIT: run = False pygame.display.flip() pygame.display.update() pygame.quit() sys.exit()