Skip to content

5. Adding Levels and Creating the Menu

DoobieD00 edited this page Mar 16, 2024 · 13 revisions

5. Making menu, and more levels

5.0: Making and Changing Levels

5.0.1: Making a second Level

To make a second level, go to the settings.py file, and you can be creative on how you want to layout the 2nd level. For simplicity we made our 2nd level in 'level_map2'.

from os import walk, path
import pygame

level_map = [
'                                                        ',
'                                                        ',
'                                    E                   ',
'       CCC        CCC CC           CCC        CCC CC    ',
'    P           C       E                 C           J ',
'              CC       CCCC              EMC       CCCC ',
'           ECC  CC         C           ECC  CC         C',
'          CCMM      CCC    M          CCMM      CCCE   M',
'          MMMM    E   MC   M          MMMM        MC   M',
'    CCCC  MMMMCC  CC  MMCCCM    CCCC EMMMMCC  CC  MMCCCM',
'CCCCMMMMCCMMMMMMCCMMCCMMMMMMCCCCMMMMCCMMMMMMCCMMCCMMMMMM'
]

level_map2 = [
'                                                        ',
'                                                        ',
'                                    E                   ',
'       CCC        CCC CC           CCC        CCC CC    ',
'    P           C       E                 C             ',
'              CC       CCCC              EMC       CCCC ',
'           ECC  CC         C           ECC  CC         C',
'          CCMM      CCC    M          CCMM      CCCE   M',
'          MMMM    E   MC   M          MMMM        MC   M',
'    CCCC  MMMMCC  CC  MMCCCM    CCCC EMMMMCC  CC  MMCCCM',
'CCCCMMMMCCMMMMMMCCMMCCMMMMMMCCCCMMMMCCMMMMMMCCMMCCMMMMMM'
]
# the base path of graphic resources of the game
base_path = path.join(path.dirname(__file__), '..', 'graphics')

# the graphics of different types of tiles
tile_types = {
    'T': path.join(base_path, 'Tiles', 'grassMid.png'),
    'X': path.join(base_path, 'Tiles', 'grassCenter.png'),
    'C': path.join(base_path, 'Tiles', 'castleMid.png'),
    'M': path.join(base_path, 'Tiles', 'castleCenter.png'),
}

# default settings
tile_size = 64
clock_tick = 1000
screen_width = 1200
screen_height = len(level_map) * tile_size

5.0.2 - Making a Door and implementing player collision

In door.py, the door class represents a door within the game, handling its creation, visual representation, and updating its position

import pygame
from settings import base_path
from os import path

class Door(pygame.sprite.Sprite):
    def __init__(self, pos):
        super().__init__()
        sprite = pygame.image.load(path.join(base_path, 'Tiles','window.png'))
        sprite_surface = pygame.transform.smoothscale(sprite, (32, 64)).convert_alpha()
        sprite_surface.set_colorkey((0, 0, 0))
        self.image = sprite_surface

        self.image.blit(sprite_surface, (0, 0))
        self.rect = self.image.get_rect(topleft=pos)

    def update(self, x_shift):
        self.rect.x += x_shift

Once you've created the door.py class, we would need to import door into level.py and add it into the setup_level method in level.py to make sure that it appears properly on the level.

from door import Door

class Level:
     
    # Other Methods
    def setup_level(self, layout):
        self.tiles = pygame.sprite.Group()
        self.player = pygame.sprite.GroupSingle()
        self.enemies = pygame.sprite.Group()
        self.doors = pygame.sprite.GroupSingle()

        for row_index, row in enumerate(layout):
            for col_index, cell in enumerate(row):
                x = col_index * tile_size
                y = row_index * tile_size

                if cell == 'P':
                    player_sprite = Player((x, y))
                    self.player.add(player_sprite)

                elif cell == 'E':
                    enemy = Enemy((x,y))
                    self.enemies.add(enemy)
                elif cell == 'J':
                    door_sprite = Door((x, y))
                    self.doors.add(door_sprite)
                elif cell != ' ':
                    tile_sprite = Tile((x, y), tile_size, cell)
                    self.tiles.add(tile_sprite)

5.0.3 - Adding a door_collision method and change_level method

Go to levels.py and add a function to check if the player has collided with the Door, then we can implement a "change_level" function to clear out the current tiles, and set up the new level. This allows the player to change levels when interacting with doors. Don't forget to import the new level.

    def check_door_collision(self):
        player = self.player.sprite
        doors = self.doors.sprites()

        for door in doors:
            if pygame.sprite.collide_rect(player, door):
                keys = pygame.key.get_pressed()
                if keys[pygame.K_SPACE]:
                    self.change_level(level_map2)  # Change the level when the player interacts with the door

    def change_level(self, level=level_map):
        # Clear all sprite groups
        self.tiles.empty()
        self.player.empty()
        self.enemies.empty()
        self.doors.empty()

        # Load the new level data
        self.setup_level(level)

    def run(self):
        #Other Methods
        self.check_door_collision()
        self.doors.draw(self.display_surface)
        self.doors.update(self.world_shift)

5.1 - Creating a Menu

5.1.1 - Making the Button Class

Essentially a start menu is just a blank screen with buttons on them, so in order to make it let's first create a button class called button.py, and inside we will have properties that will do something once someone clicks on the button.

import pygame

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, surface):
        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:
                action = True
                self.clicked = True
            if pygame.mouse.get_pressed()[0] == 0:
                self.clicked = False
        # draw button on screen
        surface.blit(self.image, self.rect)

        return action

5.1.2 - Making the Menu Screen

In video games, Menu Screens are usually found before the start of the game, like lets say a Mario game, then are we able to initialize the game. Therefore, we would need to put this function on the main.py and set it up before we are running the level!

import pygame, sys
from settings import *
from tiles import Tile
from level import Level
from button import Button
from os import path

class Main:

# Pygame setup
	def __init__(self):
		pygame.init()
		self.screen = pygame.display.set_mode((screen_width, screen_height))
		self.clock = pygame.time.Clock()
		self.level = Level(level_map, self.screen)
		self.main_menu = True
		self.start_img = pygame.image.load(path.join(base_path, 'buttons', 'start-1.png')).convert_alpha()
		self.exit_img = pygame.image.load(path.join(base_path, 'buttons', 'exit-1.png')).convert_alpha()
		self.menu_img = pygame.image.load(path.join(base_path, 'title.png')).convert_alpha()

		self.menu_background_img = pygame.transform.scale(pygame.image.load(path.join(base_path, 'sky1.png')).convert(), self.screen.get_size())
		self.game_background_img = pygame.transform.scale(pygame.image.load(path.join(base_path, 'bg.png')).convert(), self.screen.get_size())

		self.start_button = Button(screen_width//2 - self.start_img.get_width()//2, 500, self.start_img)
		self.exit_button = Button(screen_width//2 - self.exit_img.get_width()//2, 600, self.exit_img)
		pygame.display.set_caption("2D Platformer Game")


	def run(self):
		while True:
			for event in pygame.event.get():
				if event.type == pygame.QUIT:
					pygame.quit()
					sys.exit()

			self.screen.fill((0, 0, 0))  # Clear the screen with black before drawing anything

			# Check if the game is in the main menu state
			if self.main_menu:
                # Load the main menu background image
				background_img = self.menu_background_img
			else:
				# Resize the background image to match the screen size
				background_img = self.game_background_img

			# Blit the background image onto the screen
			self.screen.blit(background_img, (0, 0))

			# Handle game logic based on the current state
			if not self.main_menu:
				self.level.run()
			if not self.level.check_player():
				self.main_menu = True
				self.level.change_level(level_map)

			if self.main_menu:
				# Draw the main menu buttons
				self.screen.blit(self.menu_img, (screen_width//2 - self.menu_img.get_width()//2, 50))
				self.start_button.draw(self.screen)
				self.exit_button.draw(self.screen)

				if self.start_button.clicked:
					self.main_menu = False

				if self.exit_button.clicked:
					return 2
			else:
				self.level.run()

			pygame.display.update()
			self.clock.tick(clock_tick)

5.2 - Game Over screen

5.2.1 Adding the Death Function for Player

Go back to level.py and add a check_player() method, this will check if either the player has depleted its health, or fall into oblivion.

 def check_player(self):
        if self.player.sprite.current_health == 0 or (self.player.sprite.rect.left < 0 or self.player.sprite.rect.right > screen_width) or (self.player.sprite.rect.bottom > screen_height or self.player.sprite.rect.bottom < -64):
            return 0
        return 1

5.2.2

Return to main.py and to make a Game Over screen, we need a way of overseeing when the player dies. Once they died, we will notify the main to reset the level. The "if name=='main'" method is used at the end to check and see if there is a return value attached, then we will create a new instance (to load the resources again) therefore resetting the level. If the exit button is clicked, we will receive a "2" which will fulfill the condition and end the loop, terminating the game.

       	def run(self):
                #Other attributes 
			if not self.level.check_player():
				self.main_menu = True
				self.level.change_level(level_map)

			if self.exit_button.clicked:
				return 2

if __name__ == '__main__':
	main = Main()
	out = 0
	while out != 2:
		out = main.run()