Pygame levels / menus

With my code below, what would be the easiest and easiest way to implement game states for level management? If I would like to start from the title screen, download the level and go to the next level after completion? If someone could explain the easiest way to handle this, that would be great!

import pygame from pygame import * WIN_WIDTH = 1120 - 320 WIN_HEIGHT = 960 - 320 HALF_WIDTH = int(WIN_WIDTH / 2) HALF_HEIGHT = int(WIN_HEIGHT / 2) DISPLAY = (WIN_WIDTH, WIN_HEIGHT) DEPTH = 0 FLAGS = 0 CAMERA_SLACK = 30 def main(): global level pygame.init() screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH) pygame.display.set_caption("ABCDEFGHIJKLMNOPQRSTUVWXYZ") timer = pygame.time.Clock() level = 0 bg = Surface((32,32)) bg.convert() bg.fill(Color("#0094FF")) up = left = right = False entities = pygame.sprite.Group() player = Player(32, 32) enemy = Enemy(32,32) platforms = [] x = 0 y = 0 if level == 0: level = [ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " E ", " PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPPP PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPP P", " PPPP P", " PPPP PPPPPPP", " PPPPPPPPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPPPPP", "PPPPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPPP PPPP PPPPPPP", "PPP PPPP", "PPP PPPP", "PPP PPPP", "PPP PPPPPPPPPPPPPPPPPP", "PPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",] #background = pygame.image.load("Untitled.png") total_level_width = len(level[0]) * 32 total_level_height = len(level) * 32 # build the level for row in level: for col in row: if col == "P": p = Platform(x, y) platforms.append(p) entities.add(p) if col == "E": e = ExitBlock(x, y) platforms.append(e) entities.add(e) x += 32 y += 32 x = 0 camera = Camera(complex_camera, total_level_width, total_level_height) entities.add(player) entities.add(enemy) while 1: timer.tick(60) for e in pygame.event.get(): if e.type == QUIT: raise SystemExit, "QUIT" if e.type == KEYDOWN and e.key == K_ESCAPE: raise SystemExit, "ESCAPE" if e.type == KEYDOWN and e.key == K_UP: up = True if e.type == KEYDOWN and e.key == K_LEFT: left = True if e.type == KEYDOWN and e.key == K_RIGHT: right = True if e.type == KEYUP and e.key == K_UP: up = False if e.type == KEYUP and e.key == K_LEFT: left = False if e.type == KEYUP and e.key == K_RIGHT: right = False # draw background for y in range(20): for x in range(25): screen.blit(bg, (x * 32, y * 32)) # draw background #screen.blit(background, camera.apply((0,0))) #draw entities for e in entities: screen.blit(e.image, camera.apply(e)) # update player, update camera, and refresh player.update(up, left, right, platforms) enemy.update(platforms) camera.update(player) pygame.display.flip() class Camera(object): def __init__(self, camera_func, width, height): self.camera_func = camera_func self.state = Rect(0, 0, width, height) def apply(self, target): try: return target.rect.move(self.state.topleft) except AttributeError: return map(sum, zip(target, self.state.topleft)) def update(self, target): self.state = self.camera_func(self.state, target.rect) def complex_camera(camera, target_rect): l, t, _, _ = target_rect _, _, w, h = camera l, t, _, _ = -l + HALF_WIDTH, -t +HALF_HEIGHT, w, h l = min(0, l) # stop scrolling left l = max(-(camera.width - WIN_WIDTH), l) # stop scrolling right t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling bottom return Rect(l, t, w, h) class Entity(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) class Player(Entity): def __init__(self, x, y): Entity.__init__(self) self.xvel = 0 self.yvel = 0 self.onGround = False self.image = Surface((32,32)) self.image.fill(Color("#0000FF")) self.image.convert() self.rect = Rect(200, 1200, 32, 32) def update(self, up, left, right, platforms): if self.rect.top > 1440 or self.rect.top < 0: main() if self.rect.left > 1408 or self.rect.right < 0: main() if up: if self.onGround: self.yvel = 0 self.yvel -= 10 # only jump if on the ground if left: self.xvel = -10 if right: self.xvel = 10 if not self.onGround: self.yvel += 0.3 # only accelerate with gravity if in the air if self.yvel > 80: self.yvel = 80 # max falling speed if not(left or right): self.xvel = 0 self.rect.left += self.xvel # increment in x direction self.collide(self.xvel, 0, platforms) # do x-axis collisions self.rect.top += self.yvel # increment in y direction self.onGround = False; # assuming we're in the air self.collide(0, self.yvel, platforms) # do y-axis collisions def collide(self, xvel, yvel, platforms): for p in platforms: if pygame.sprite.collide_rect(self, p): if isinstance(p, ExitBlock): pygame.event.post(pygame.event.Event(QUIT)) if xvel > 0: self.rect.right = p.rect.left if xvel < 0: self.rect.left = p.rect.right if yvel > 0: self.rect.bottom = p.rect.top self.onGround = True if yvel < 0: self.rect.top = p.rect.bottom class Enemy(Entity): def __init__(self, x, y): Entity.__init__(self) self.yVel = 0 self.xVel = 0 self.image = Surface((32,32)) self.image.fill(Color("#00FF00")) self.image.convert() self.rect = Rect(300, 1200, 32, 32) self.onGround = False self.right_dis = False def update(self, platforms): if not self.onGround: self.yVel += 0.3 if self.rect.left == 96: self.right_dis = False if self.rect.right == 480: self.right_dis = True if not self.right_dis: self.xVel = 2 if self.right_dis: self.xVel = -2 self.rect.left += self.xVel # increment in x direction self.collide(self.xVel, 0, platforms) # do x-axis collisions self.rect.top += self.yVel # increment in y direction self.onGround = False; # assuming we're in the air self.collide(0, self.yVel, platforms) # do y-axis collisions def collide(self, xVel, yVel, platforms): for p in platforms: if pygame.sprite.collide_rect(self, p): if xVel > 0: self.rect.right = p.rect.left if xVel < 0: self.rect.left = p.rect.right if yVel > 0: self.rect.bottom = p.rect.top self.onGround = True if yVel < 0: self.rect.top = p.rect.bottom class Platform(Entity): def __init__(self, x, y): Entity.__init__(self) #self.image = Surface([32, 32], pygame.SRCALPHA, 32) #makes blocks invisible for much better artwork self.image = Surface((32,32)) #makes blocks visible for building levels self.image.convert() self.rect = Rect(x, y, 32, 32) def update(self): pass class ExitBlock(Platform): def __init__(self, x, y): Platform.__init__(self, x, y) self.image = pygame.image.load("end.png") if __name__ == "__main__": main() 
+9
source share
2 answers

First of all, get rid of these ugly if-blocks:

 for e in pygame.event.get(): if e.type == QUIT: raise SystemExit, "QUIT" if e.type == KEYDOWN and e.key == K_ESCAPE: raise SystemExit, "ESCAPE" if e.type == KEYDOWN and e.key == K_UP: up = True if e.type == KEYDOWN and e.key == K_LEFT: left = True if e.type == KEYDOWN and e.key == K_RIGHT: right = True if e.type == KEYUP and e.key == K_UP: up = False if e.type == KEYUP and e.key == K_LEFT: left = False if e.type == KEYUP and e.key == K_RIGHT: right = False 

We can rewrite them as:

 for e in pygame.event.get(): if e.type == QUIT: raise SystemExit, "QUIT" if e.type == KEYDOWN and e.key == K_ESCAPE: raise SystemExit, "ESCAPE" pressed = pygame.key.get_pressed() up, left, right = [pressed[key] for key in (K_UP, K_LEFT, K_RIGHT)] 

This will be useful later.


Back to the section: What we need is a bunch of different scenes. Each scene should be responsible for its own screen rendering and event handling.

Try extracting the existing code into the game scene so that you can add other scenes later. Let's start by creating an empty Scene class, which will be the base class of our scenes:

 class Scene(object): def __init__(self): pass def render(self, screen): raise NotImplementedError def update(self): raise NotImplementedError def handle_events(self, events): raise NotImplementedError 

Our plan is to overwrite each method in each subclass, so we raise NotImplementedError in the base class, so we easily detect if we forget to do this (we could also use ABC, but let it be simple).

Now let's put everything related to the state of the running game (this is basically all) in the new GameScene class.

 class GameScene(Scene): def __init__(self): super(GameScene, self).__init__() level = 0 self.bg = Surface((32,32)) self.bg.convert() self.bg.fill(Color("#0094FF")) up = left = right = False self.entities = pygame.sprite.Group() self.player = Player(32, 32) self.enemy = Enemy(32,32) self.platforms = [] x = 0 y = 0 if level == 0: level = [ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " E ", " PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPPP PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPP P", " PPPP P", " PPPP PPPPPPP", " PPPPPPPPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPPPPP", "PPPPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPPP PPPP PPPPPPP", "PPP PPPP", "PPP PPPP", "PPP PPPP", "PPP PPPPPPPPPPPPPPPPPP", "PPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",] #background = pygame.image.load("Untitled.png") total_level_width = len(level[0]) * 32 total_level_height = len(level) * 32 # build the level for row in level: for col in row: if col == "P": p = Platform(x, y) self.platforms.append(p) self.entities.add(p) if col == "E": e = ExitBlock(x, y) self.platforms.append(e) self.entities.add(e) x += 32 y += 32 x = 0 self.camera = Camera(complex_camera, total_level_width, total_level_height) self.entities.add(self.player) self.entities.add(self.enemy) def render(self, screen): for y in range(20): for x in range(25): screen.blit(self.bg, (x * 32, y * 32)) for e in self.entities: screen.blit(e.image, self.camera.apply(e)) def update(self): pressed = pygame.key.get_pressed() up, left, right = [pressed[key] for key in (K_UP, K_LEFT, K_RIGHT)] self.player.update(up, left, right, self.platforms) self.enemy.update(self.platforms) self.camera.update(self.player) def handle_events(self, events): for e in events: if e.type == KEYDOWN and e.key == K_ESCAPE: pass #somehow go back to menu 

Not perfect, but a good start. Everything related to the real gameplay is retrieved in its own class. Some variables must be instance variables, so they must be accessed through self .

Now we need to change the main function to actually use this class:

 def main(): pygame.init() screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH) pygame.display.set_caption("ABCDEFGHIJKLMNOPQRSTUVWXYZ") timer = pygame.time.Clock() running = True scene = GameScene() while running: timer.tick(60) if pygame.event.get(QUIT): running = False return scene.handle_events(pygame.event.get()) scene.update() scene.render(screen) pygame.display.flip() 

Please note that I changed two little things: I use pygame.event.get(QUIT) to get a possible QUIT -event first, since this is the only event that we enter in our main loop. All other events are sent directly to the current scene: scene.handle_events(pygame.event.get()) .

At this point, we might consider extracting some classes into our own files, but continue instead.

Let me create a heading menu:

 class TitleScene(object): def __init__(self): super(TitleScene, self).__init__() self.font = pygame.font.SysFont('Arial', 56) self.sfont = pygame.font.SysFont('Arial', 32) def render(self, screen): # beware: ugly! screen.fill((0, 200, 0)) text1 = self.font.render('Crazy Game', True, (255, 255, 255)) text2 = self.sfont.render('> press space to start <', True, (255, 255, 255)) screen.blit(text1, (200, 50)) screen.blit(text2, (200, 350)) def update(self): pass def handle_events(self, events): for e in events: if e.type == KEYDOWN and e.key == K_SPACE: self.manager.go_to(GameScene(0)) 

Only green background and text are displayed here. If a player presses SPACE , we want to start the first level. Pay attention to this line:

 self.manager.go_to(GameScene(0)) 

Here I pass the argument 0 the GameScene class, so let it change it, so it takes this parameter:

 class GameScene(Scene): def __init__(self, level): ... 

line level = 0 can be deleted, as you might have guessed.

So what is this self.manager ? This is just a small helper class that manages the scenes for us.

 class SceneMananger(object): def __init__(self): self.go_to(TitleScene()) def go_to(self, scene): self.scene = scene self.scene.manager = self 

It starts with a plot scene and sets the manager field for each episode to itself, to allow changing the current scene. There are many possibilities how to implement such a scene manager, and this is the easiest approach. The disadvantage is that each scene must know which scene occurs after it, but this should not bother us right now.

Let us use our new SceneMananger :

 def main(): pygame.init() screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH) pygame.display.set_caption("ABCDEFGHIJKLMNOPQRSTUVWXYZ") timer = pygame.time.Clock() running = True manager = SceneMananger() while running: timer.tick(60) if pygame.event.get(QUIT): running = False return manager.scene.handle_events(pygame.event.get()) manager.scene.update() manager.scene.render(screen) pygame.display.flip() 

just. Let me quickly add a second level and a winning / losing screen, and we are done.

 class CustomScene(object): def __init__(self, text): self.text = text super(CustomScene, self).__init__() self.font = pygame.font.SysFont('Arial', 56) def render(self, screen): # ugly! screen.fill((0, 200, 0)) text1 = self.font.render(self.text, True, (255, 255, 255)) screen.blit(text1, (200, 50)) def update(self): pass def handle_events(self, events): for e in events: if e.type == KEYDOWN: self.manager.go_to(TitleScene()) 

Below is the full code. Note the changes in the Player class: instead of calling the main function, it calls methods on the scene to indicate that the player has reached the exit or has died.

In addition, I changed the location of the player and enemies. Now you indicate where the object appears in the level. For instance. Player(5, 40) will create the player in column 5, row 40 at level. As a bonus, enemies to correctly detect collisions.

I extracted the level description into a dictionary called levels , so it will be easy to change and add levels as needed (later you will probably want one file per level, so this is a good start). It can be expanded to hold the player’s initial position, but you can also create a special tile, for example * for the initial position and E for the opponent in the level description.

 import pygame from pygame import * WIN_WIDTH = 1120 - 320 WIN_HEIGHT = 960 - 320 HALF_WIDTH = int(WIN_WIDTH / 2) HALF_HEIGHT = int(WIN_HEIGHT / 2) DISPLAY = (WIN_WIDTH, WIN_HEIGHT) DEPTH = 0 FLAGS = 0 CAMERA_SLACK = 30 levels = {0: {'level': [ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " E ", " PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPPP PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPP P", " PPPP P", " PPPP PPPPPPP", " PPPPPPPPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPPPPP", "PPPPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPPP PPPP PPPPPPP", "PPP PPPP", "PPP PPPP", "PPP PPPP", "PPP PPPPPPPPPPPPPPPPPP", "PPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",], 'enemies': [(9, 38)]}, 1: {'level': [ " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " E ", " PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPPP PPPPPPPPPPPPPPPP", " PPPPPPPPPPPPPPPP", " PPPP P", " PPPP P", " PPPP PPPPPPP", " PPPPPPPPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPPPPP", " PPPP PPPPPPP", "PPPPP PPPP PPPPPPP", "PPP PPPPPPPPPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPP PPPPPPP", "PPP PPPPPPPP PPPP PPPPPPP", "PPP PPPP", "PPP PPPP", "PPP PPPPP PPPP", "PPP P PPPPPPPPPPPPPPPPPP", "PPP P PPPPPPPPPPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP", "PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",], 'enemies': [(9, 38), (18, 38), (15, 15)]}} class Scene(object): def __init__(self): pass def render(self, screen): raise NotImplementedError def update(self): raise NotImplementedError def handle_events(self, events): raise NotImplementedError class GameScene(Scene): def __init__(self, levelno): super(GameScene, self).__init__() self.bg = Surface((32,32)) self.bg.convert() self.bg.fill(Color("#0094FF")) up = left = right = False self.entities = pygame.sprite.Group() self.player = Player(5, 40) self.player.scene = self self.platforms = [] self.levelno = levelno levelinfo = levels[levelno] self.enemies = [Enemy(*pos) for pos in levelinfo['enemies']] level = levelinfo['level'] total_level_width = len(level[0]) * 32 total_level_height = len(level) * 32 # build the level x = 0 y = 0 for row in level: for col in row: if col == "P": p = Platform(x, y) self.platforms.append(p) self.entities.add(p) if col == "E": e = ExitBlock(x, y) self.platforms.append(e) self.entities.add(e) x += 32 y += 32 x = 0 self.camera = Camera(complex_camera, total_level_width, total_level_height) self.entities.add(self.player) for e in self.enemies: self.entities.add(e) def render(self, screen): for y in range(20): for x in range(25): screen.blit(self.bg, (x * 32, y * 32)) for e in self.entities: screen.blit(e.image, self.camera.apply(e)) def update(self): pressed = pygame.key.get_pressed() up, left, right = [pressed[key] for key in (K_UP, K_LEFT, K_RIGHT)] self.player.update(up, left, right, self.platforms) for e in self.enemies: e.update(self.platforms) self.camera.update(self.player) def exit(self): if self.levelno+1 in levels: self.manager.go_to(GameScene(self.levelno+1)) else: self.manager.go_to(CustomScene("You win!")) def die(self): self.manager.go_to(CustomScene("You lose!")) def handle_events(self, events): for e in events: if e.type == KEYDOWN and e.key == K_ESCAPE: self.manager.go_to(TitleScene()) class CustomScene(object): def __init__(self, text): self.text = text super(CustomScene, self).__init__() self.font = pygame.font.SysFont('Arial', 56) def render(self, screen): # ugly! screen.fill((0, 200, 0)) text1 = self.font.render(self.text, True, (255, 255, 255)) screen.blit(text1, (200, 50)) def update(self): pass def handle_events(self, events): for e in events: if e.type == KEYDOWN: self.manager.go_to(TitleScene()) class TitleScene(object): def __init__(self): super(TitleScene, self).__init__() self.font = pygame.font.SysFont('Arial', 56) self.sfont = pygame.font.SysFont('Arial', 32) def render(self, screen): # ugly! screen.fill((0, 200, 0)) text1 = self.font.render('Crazy Game', True, (255, 255, 255)) text2 = self.sfont.render('> press space to start <', True, (255, 255, 255)) screen.blit(text1, (200, 50)) screen.blit(text2, (200, 350)) def update(self): pass def handle_events(self, events): for e in events: if e.type == KEYDOWN and e.key == K_SPACE: self.manager.go_to(GameScene(0)) class SceneMananger(object): def __init__(self): self.go_to(TitleScene()) def go_to(self, scene): self.scene = scene self.scene.manager = self def main(): pygame.init() screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH) pygame.display.set_caption("ABCDEFGHIJKLMNOPQRSTUVWXYZ") timer = pygame.time.Clock() running = True manager = SceneMananger() while running: timer.tick(60) if pygame.event.get(QUIT): running = False return manager.scene.handle_events(pygame.event.get()) manager.scene.update() manager.scene.render(screen) pygame.display.flip() class Camera(object): def __init__(self, camera_func, width, height): self.camera_func = camera_func self.state = Rect(0, 0, width, height) def apply(self, target): try: return target.rect.move(self.state.topleft) except AttributeError: return map(sum, zip(target, self.state.topleft)) def update(self, target): self.state = self.camera_func(self.state, target.rect) def complex_camera(camera, target_rect): l, t, _, _ = target_rect _, _, w, h = camera l, t, _, _ = -l + HALF_WIDTH, -t +HALF_HEIGHT, w, h l = min(0, l) # stop scrolling left l = max(-(camera.width - WIN_WIDTH), l) # stop scrolling right t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling bottom return Rect(l, t, w, h) class Entity(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) class Player(Entity): def __init__(self, x, y): Entity.__init__(self) self.xvel = 0 self.yvel = 0 self.onGround = False self.image = Surface((32,32)) self.image.fill(Color("#0000FF")) self.image.convert() self.rect = Rect(x*32, y*32, 32, 32) def update(self, up, left, right, platforms): if self.rect.top > 1440 or self.rect.top < 0: self.scene.die() if self.rect.left > 1408 or self.rect.right < 0: self.scene.die() if up: if self.onGround: self.yvel = 0 self.yvel -= 10 # only jump if on the ground if left: self.xvel = -10 if right: self.xvel = 10 if not self.onGround: self.yvel += 0.3 # only accelerate with gravity if in the air if self.yvel > 80: self.yvel = 80 # max falling speed if not(left or right): self.xvel = 0 self.rect.left += self.xvel # increment in x direction if self.collide(self.xvel, 0, platforms): # do x-axis collisions self.rect.top += self.yvel # increment in y direction self.onGround = False; # assuming we're in the air self.collide(0, self.yvel, platforms) # do y-axis collisions def collide(self, xvel, yvel, platforms): for p in platforms: if pygame.sprite.collide_rect(self, p): if isinstance(p, ExitBlock): self.scene.exit() return False if xvel > 0: self.rect.right = p.rect.left if xvel < 0: self.rect.left = p.rect.right if yvel > 0: self.rect.bottom = p.rect.top self.onGround = True if yvel < 0: self.rect.top = p.rect.bottom return True class Enemy(Entity): def __init__(self, x, y): Entity.__init__(self) self.yVel = 0 self.xVel = 2 # start moving immediately self.image = Surface((32,32)) self.image.fill(Color("#00FF00")) self.image.convert() self.rect = Rect(x*32, y*32, 32, 32) self.onGround = False def update(self, platforms): if not self.onGround: self.yVel += 0.3 # no need for right_dis to be a member of the class, # since we know we are moving right if self.xVel > 0 right_dis = self.xVel > 0 # create a point at our left (or right) feet # to check if we reached the end of the platform m = (1, 1) if right_dis else (-1, 1) p = self.rect.bottomright if right_dis else self.rect.bottomleft fp = map(sum, zip(m, p)) # if there no platform in front of us, change the direction collide = any(p for p in platforms if p.rect.collidepoint(fp)) if not collide: self.xVel *= -1 self.rect.left += self.xVel # increment in x direction self.collide(self.xVel, 0, platforms) # do x-axis collisions self.rect.top += self.yVel # increment in y direction self.onGround = False; # assuming we're in the air self.collide(0, self.yVel, platforms) # do y-axis collisions def collide(self, xVel, yVel, platforms): for p in platforms: if pygame.sprite.collide_rect(self, p): if xVel > 0: self.rect.right = p.rect.left self.xVel *= -1 # hit wall, so change direction if xVel < 0: self.rect.left = p.rect.right self.xVel *= -1 # hit wall, so change direction if yVel > 0: self.rect.bottom = p.rect.top self.onGround = True if yVel < 0: self.rect.top = p.rect.bottom class Platform(Entity): def __init__(self, x, y): Entity.__init__(self) #self.image = Surface([32, 32], pygame.SRCALPHA, 32) #makes blocks invisible for much better artwork self.image = Surface((32,32)) #makes blocks visible for building levels self.image.convert() self.rect = Rect(x, y, 32, 32) def update(self): pass class ExitBlock(Platform): def __init__(self, x, y): Platform.__init__(self, x, y) self.image = Surface((32,32)) #makes blocks visible for building levels self.image.convert() self.rect = Rect(x, y, 32, 32) if __name__ == "__main__": main() 
+33
source

I also created a game in which I have a game menu, level menu, boot part and game part.
The way I did it was in the main game loop,
I went through a bunch of elif statements to determine which "mode" the game is in and does the appropriate action.
It seemed to work very well, and I suggest you try the same thing.

I understand that my code is very long, but if you go where it says #game loop (use ctrl + f to find it), you can see elifs to determine the mode. Hope this helps.

 #basic stuff import pygame, sys, random pygame.init() window=pygame.display.set_mode((1500, 800), pygame.FULLSCREEN) winrect=window.get_rect() #colors GREEN=(19, 225, 30) BLUE=(41, 2, 245) YELLOW=(251, 240, 32) WHITE=(255, 255, 255) BLACK=(0, 0, 0) RED=(255, 0, 0) #text bigfont=pygame.font.SysFont('calibri', 75) font=pygame.font.SysFont('calibri', 40) texts={} so=bigfont.render('Ball Bounce', True, BLUE) rect=so.get_rect() rect.top=winrect.top+100 rect.centerx=winrect.centerx texts['title']=[so, rect] so=font.render('Start', True, BLUE) rect=so.get_rect() so1=pygame.Surface((400, 50)) so2=pygame.Surface((400, 50)) rect1=so1.get_rect() so1.fill(YELLOW) so2.fill(RED) pygame.draw.rect(so1, BLACK, rect1, 5) pygame.draw.rect(so2, BLACK, rect1, 5) rect.center=rect1.center so1.blit(so, rect) so2.blit(so, rect) rect1.centerx=winrect.centerx rect1.top=texts['title'][1].top+300 texts['start']=[so1, rect1, so2] so=bigfont.render('Levels', True, BLUE) rect=so.get_rect() rect.centerx=winrect.centerx rect.top=winrect.top+100 texts['levels']=[so, rect] #levels [locked, unlocked, completed/mouseover, rect, state(locked, unlocked, completed)] levels=[] lock=pygame.image.load('images/lock.png').convert() lock=pygame.transform.scale(lock, (100, 100)) lock.set_colorkey(lock.get_at((1, 1))) for i in range(1, 21): so=pygame.Surface((100, 100)) so.fill(YELLOW) rect=so.get_rect() pygame.draw.rect(so, BLACK, rect, 5) so1=pygame.Surface((100, 100)) so1.fill(RED) pygame.draw.rect(so1, BLACK, rect, 5) text=font.render(str(i), True, BLUE) textrect=text.get_rect() textrect.center=rect.center so.blit(text, textrect) so1.blit(text, textrect) locked=pygame.Surface((100, 100)) locked.blit(so, rect) locked.blit(lock, lock.get_rect()) if i<=5: rect.top=texts['levels'][1].bottom+25 elif i<=10: rect.top=levels[0][3].bottom+50 elif i<=15: rect.top=levels[7][3].bottom+50 else: rect.top=levels[12][3].bottom+50 if i==1 or i==6 or i==11 or i==16: rect.right=winrect.centerx-200 elif i==2 or i==7 or i==12 or i==17: rect.right=winrect.centerx-75 elif i==3 or i==8 or i==13 or i==18: rect.centerx=winrect.centerx elif i==4 or i==9 or i==14 or i==19: rect.left=winrect.centerx+75 else: rect.left=winrect.centerx+200 if i==1: levels.append([locked, so, so1, rect, 1]) else: levels.append([locked, so, so1, rect, 1]) #Wall class (0=horizontal, 1=vertical) class cwall(pygame.Rect): 'orientation (hor, vert), location, holesize, winrect' def __init__(self, orientation, location, holesize, winrect): self.orientation=orientation if orientation==0: self.height=5 self.width=winrect.width self.centery=location if orientation==1: self.width=5 self.height=winrect.height self.centerx=location self.holesize=holesize self.bbottomright=round(pygame.mouse.get_pos()[self.orientation]+self.holesize/2) self.ttopleft=round(pygame.mouse.get_pos()[self.orientation]-self.holesize/2) def update(self): self.bbottomright=round(pygame.mouse.get_pos()[self.orientation]+self.holesize/2) self.ttopleft=round(pygame.mouse.get_pos()[self.orientation]-self.holesize/2) if self.bbottomright<self.holesize: self.bbottomright=self.holesize if self.ttopleft>self.right-self.holesize and self.orientation==0: self.ttopleft=self.right-self.holesize if self.ttopleft>self.bottom-self.holesize and self.orientation==1: self.ttopleft=self.bottom-self.holesize #Ball Class class cball(pygame.Rect): 'diameter, speed, color, winrect' def __init__(self, diameter, speed, color, winrect): self.width=diameter self.height=diameter self.speed=speed self.color=color self.direction=random.randint(1, 4) self.center=(random.randint(round(diameter/2), round(winrect.right-diameter/2)), random.randint(round(diameter/2), round(winrect.bottom-diameter/2))) def update(self, winrect, walls): if self.direction/2==round(self.direction/2): self.right+=self.speed else: self.right-=self.speed if self.direction<=2: self.top+=self.speed else: self.top-=self.speed for wall in walls: if wall.collidepoint(self.center): if wall.orientation==0 and (self.centerx<wall.ttopleft or self.centerx>wall.bbottomright): if self.direction==1: self.direction=3 self.bottom=wall.top elif self.direction==2: self.direction=4 self.bottom=wall.top elif self.direction==3: self.direction=1 self.top=wall.bottom else: self.direction=2 self.top=wall.bottom elif wall.orientation==1 and (self.centery<wall.ttopleft or self.centery>wall.bbottomright): if self.direction==1: self.direction=2 self.left=wall.right elif self.direction==2: self.direction=1 self.right=wall.left elif self.direction==3: self.direction=4 self.left=wall.right else: self.direction=3 self.right=wall.left elif wall.orientation==0: if self.bottom>wall.top and self.centery<wall.top and (self.centerx<wall.ttopleft or self.centerx>wall.bbottomright): if self.direction==1: self.direction=3 self.bottom=wall.top elif self.direction==2: self.direction=4 self.bottom=wall.top elif self.top<wall.bottom and self.centery>wall.bottom and (self.centerx<wall.ttopleft or self.centerx>wall.bbottomright): if self.direction==3: self.direction=1 self.top=wall.bottom if self.direction==4: self.direction=2 self.top=wall.bottom else: if self.left<wall.right and self.centerx>wall.right and (self.centery<wall.ttopleft or self.centery>wall.bbottomright): if self.direction==1: self.direction=2 self.left=wall.right elif self.direction==3: self.direction=4 self.left=wall.right elif self.right>wall.left and self.centerx<wall.left and (self.centery<wall.ttopleft or self.centery>wall.bbottomright): if self.direction==2: self.direction=1 self.right=wall.left if self.direction==4: self.direction=3 self.right=wall.left if self.top<0: if self.direction==3: self.direction=1 self.top=0 elif self.direction==4: self.direction=2 self.topn=0 if self.bottom>winrect.bottom: if self.direction==1: self.direction=3 self.bottom=winrect.bottom elif self.direction==2: self.direction=4 self.bottom=winrect.bottom if self.left<0: if self.direction==1: self.direction=2 self.left=0 elif self.direction==3: self.direction=4 self.left=0 if self.right>winrect.right: if self.direction==2: self.direction=1 self.right=winrect.right if self.direction==4: self.direction=3 self.right=winrect.right for box in boxes: if box[0].collidepoint(self.center) and self.color==box[1]: return True return False #Game loop setup mode='title' #Game loop while True: if mode=='title': #event loop for event in pygame.event.get(): if event.type==pygame.QUIT: pygame.quit() sys.exit() if event.type==pygame.MOUSEBUTTONDOWN: if texts['start'][1].collidepoint(event.pos): mode='levels' if event.type==pygame.KEYDOWN: if event.key==pygame.K_ESCAPE: pygame.quit() sys.exit() #screen update window.fill(GREEN) mouse=pygame.mouse.get_pos() if texts['start'][1].collidepoint(mouse): window.blit(texts['start'][2], texts['start'][1]) else: window.blit(texts['start'][0], texts['start'][1]) window.blit(texts['title'][0], texts['title'][1]) pygame.display.update() elif mode=='levels': #event loop for event in pygame.event.get(): if event.type==pygame.QUIT: pygame.quit() sys.exit() if event.type==pygame.MOUSEBUTTONDOWN: for level in levels: if level[3].collidepoint(event.pos) and level[4]!=0: mode='loading' loadinglevel=levels.index(level)+1 if event.type==pygame.KEYDOWN: if event.key==pygame.K_ESCAPE: pygame.quit() sys.exit() #screen update window.fill(GREEN) for level in levels: if level[3].collidepoint(pygame.mouse.get_pos()) and level[4]==1: window.blit(level[2], level[3]) else: window.blit(level[level[4]], level[3]) window.blit(texts['levels'][0], texts['levels'][1]) pygame.display.update() elif mode=='loading': if loadinglevel==1: walls=[cwall(1, winrect.width/2, 100, winrect)] balls=[] for i in range(2): balls.append(cball(20, 3, GREEN, winrect)) for i in range(2): balls.append(cball(20, 3, YELLOW, winrect)) boxes=((pygame.Rect(0, 0, round(winrect.width/2), winrect.height), GREEN), (pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), winrect.height), YELLOW)) elif loadinglevel==2: walls=[cwall(1, winrect.width/2, 100, winrect)] balls=[] for i in range(4): balls.append(cball(20, 3, GREEN, winrect)) for i in range(4): balls.append(cball(20, 3, YELLOW, winrect)) boxes=((pygame.Rect(0, 0, round(winrect.width/2), winrect.height), GREEN), (pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), winrect.height), YELLOW)) elif loadinglevel==3: walls=[cwall(1, winrect.width/3, 100, winrect), cwall(1, 2*winrect.width/3, 100, winrect)] balls=[] for i in range(2): balls.append(cball(20, 3, GREEN, winrect)) for i in range(2): balls.append(cball(20, 3, YELLOW, winrect)) for i in range(2): balls.append(cball(20, 3, BLUE, winrect)) boxes=((pygame.Rect(0, 0, round(winrect.width/3), winrect.height), GREEN), (pygame.Rect(round(winrect.width/3), 0, round(winrect.width/3), winrect.height), YELLOW), (pygame.Rect(round(2*winrect.width/3), 0, round(winrect.width/3), winrect.height), BLUE)) elif loadinglevel==4: walls=[cwall(1, winrect.width/3, 100, winrect), cwall(1, 2*winrect.width/3, 100, winrect)] balls=[] for i in range(4): balls.append(cball(20, 3, GREEN, winrect)) for i in range(4): balls.append(cball(20, 3, YELLOW, winrect)) for i in range(4): balls.append(cball(20, 3, BLUE, winrect)) boxes=((pygame.Rect(0, 0, round(winrect.width/3), winrect.height), GREEN), (pygame.Rect(round(winrect.width/3), 0, round(winrect.width/3), winrect.height), YELLOW), (pygame.Rect(round(2*winrect.width/3), 0, round(winrect.width/3), winrect.height), BLUE)) elif loadinglevel==7: walls=[cwall(1, winrect.width/2, 100, winrect), cwall(0, winrect.height/2, 100, winrect)] balls=[] for i in range(2): balls.append(cball(20, 3, GREEN, winrect)) for i in range(2): balls.append(cball(20, 3, YELLOW, winrect)) for i in range(2): balls.append(cball(20, 3, BLUE, winrect)) for i in range(2): balls.append(cball(20, 3, RED, winrect)) boxes=((pygame.Rect(0, 0, round(winrect.width/2), round(winrect.height/2)), GREEN), (pygame.Rect(0, round(winrect.height/2), round(winrect.width/2), round(winrect.height/2)), RED), (pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), round(winrect.height/2)), YELLOW), (pygame.Rect(round(winrect.width/2), round(winrect.height/2), round(winrect.width/2), round(winrect.height/2)), BLUE)) elif loadinglevel==8: walls=[cwall(1, winrect.width/2, 100, winrect), cwall(0, winrect.height/2, 100, winrect)] balls=[] for i in range(4): balls.append(cball(20, 3, GREEN, winrect)) for i in range(4): balls.append(cball(20, 3, YELLOW, winrect)) for i in range(4): balls.append(cball(20, 3, BLUE, winrect)) for i in range(4): balls.append(cball(20, 3, RED, winrect)) boxes=((pygame.Rect(0, 0, round(winrect.width/2), round(winrect.height/2)), GREEN), (pygame.Rect(0, round(winrect.height/2), round(winrect.width/2), round(winrect.height/2)), RED), (pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), round(winrect.height/2)), YELLOW), (pygame.Rect(round(winrect.width/2), round(winrect.height/2), round(winrect.width/2), round(winrect.height/2)), BLUE)) elif loadinglevel==5: walls=[cwall(1, winrect.width/2, 100, winrect), cwall(0, winrect.height/2, 100, winrect)] balls=[] for i in range(10): balls.append(cball(20, 3, RED, winrect)) boxes=((pygame.Rect(0, 0, round(winrect.width/2), round(winrect.height/2)), RED), (pygame.Rect(0, round(winrect.height/2), winrect.width, round(winrect.height/2)), WHITE), (pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), winrect.height), WHITE)) elif loadinglevel==6: walls=[cwall(1, winrect.width/2, 100, winrect), cwall(0, winrect.height/2, 100, winrect)] balls=[] for i in range(20): balls.append(cball(20, 3, RED, winrect)) boxes=((pygame.Rect(0, 0, round(winrect.width/2), round(winrect.height/2)), RED), (pygame.Rect(0, round(winrect.height/2), winrect.width, round(winrect.height/2)), WHITE), (pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), winrect.height), WHITE)) mode='playing' elif mode=='playing': while True: #event loop for event in pygame.event.get(): if event.type==pygame.QUIT: pygame.quit() sys.exit() if event.type==pygame.KEYDOWN: if event.key==pygame.K_ESCAPE: pygame.quit() sys.exit() #updates updates=[] for wall in walls: wall.update() for ball in balls: updates.append(ball.update(winrect, walls)) #Seeing if won won=True for update in updates: if not update: won=False break if won: if levels[loadinglevel][4]==0: levels[loadinglevel][4]=1 levels[loadinglevel-1][4]=2 mode='levels' break #blitting window.fill(WHITE) for box in boxes: pygame.draw.rect(window, box[1], box[0]) for wall in walls: if wall.orientation==0: pygame.draw.rect(window, BLACK, (wall.left, wall.top, wall.ttopleft, wall.height)) pygame.draw.rect(window, BLACK, (wall.bbottomright, wall.top, wall.right-wall.bbottomright, wall.height)) else: pygame.draw.rect(window, BLACK, (wall.left, wall.top, wall.width, wall.ttopleft)) pygame.draw.rect(window, BLACK, (wall.left, wall.bbottomright, wall.width, wall.bottom-wall.holesize)) for ball in balls: pygame.draw.circle(window, ball.color, ball.center, round(ball.width/2)) pygame.draw.circle(window, BLACK, ball.center, round(ball.width/2), 2) pygame.display.update() pygame.time.Clock().tick(100) 
+1
source

All Articles