Уровни Pygame / меню
С моим кодом ниже, какой будет самый простой и простой способ реализации игровых состояний для управления уровнями? Если бы я хотел начать с экрана заголовка, загрузите уровень и перейдите на следующий уровень после завершения? Если бы кто-то мог объяснить самый простой способ справиться с этим, это было бы здорово!
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 1408 or self.rect.right 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.bottom = p.rect.top self.onGround = True if yvel 0: self.rect.right = p.rect.left if xVel 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()
Прежде всего, давайте избавимся от этих уродливых if-блоков:
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
Мы можем переписать их как:
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)]
Это будет полезно позже.
Вернуться к разделу: Что нам нужно, это куча разных сцен . Каждая сцена должна отвечать за собственный рендеринг экрана и обработки событий.
Попробуем извлечь существующий код в игровую сцену , чтобы позже можно было добавить другие сцены. Начнем с создания пустого classа Scene
который будет базовым classом наших сцен:
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е, поэтому мы поднимаем NotImplementedError
s в базовом classе, поэтому мы легко обнаруживаем, если мы забудем это сделать (мы также могли бы использовать ABC, но давайте оставим его простыми).
Теперь давайте поместим все, что связано с состоянием бегущей игры (в основном это все) в новый class GameScene
.
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
Еще не идеальный, но хороший старт. Все, что связано с настоящим геймплеем, извлекается в собственный class. Некоторые переменные должны быть переменными экземпляра, поэтому к ним нужно обращаться через self
.
Теперь нам нужно изменить main
функцию, чтобы фактически использовать этот 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()
Обратите внимание, что я изменил две мелочи: я использую pygame.event.get(QUIT)
чтобы получить сначала QUIT
-event, так как это единственное событие, которое мы вступаем в наш основной цикл. Все остальные события передаются непосредственно на текущую сцену: scene.handle_events(pygame.event.get())
.
На данный момент мы могли бы подумать об извлечении некоторых classов в свои собственные файлы, но давайте продолжим.
Давайте создадим меню заголовков:
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))
Это просто отображает зеленый фон и некоторый текст. Если игрок нажимает SPACE , мы хотим начать первый уровень. Обратите внимание на эту строку:
self.manager.go_to(GameScene(0))
Здесь я GameScene
аргумент 0
classу GameScene
, поэтому давайте его изменим, чтобы он принял этот параметр:
class GameScene(Scene): def __init__(self, level): ...
level = 0
линии level = 0
можно удалить, как вы уже догадались.
Итак, что это за self.manager
? Это всего лишь небольшой class помощников, который управляет сценами для нас.
class SceneMananger(object): def __init__(self): self.go_to(TitleScene()) def go_to(self, scene): self.scene = scene self.scene.manager = self
Он начинается с сюжетной сцены и устанавливает поле каждого manager
сцен для себя, чтобы разрешить смену текущей сцены. Есть много возможностей, как реализовать такой менеджер сцены, и это самый простой подход. Недостатком является то, что каждая сцена должна знать, какая сцена происходит после нее, но это не должно нас беспокоить прямо сейчас.
Давайте использовать наш новый 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()
простой. Давайте быстро добавим второй уровень и выигрышный / проигрывающий экран, и мы закончили.
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е Player
: вместо повторного вызова main
функции он вызывает методы на сцене, чтобы указать, что игрок достиг выхода или умер.
Кроме того, я изменил расположение игрока и врагов. Теперь вы указываете, где объект появится на уровне. Например, Player(5, 40)
создаст игрока в столбце 5, строка 40 на уровне. В качестве бонуса, враги к правильному обнаружению столкновений.
Я извлек описание уровней в словарь с именованными levels
, поэтому будет легко изменить и добавить уровни по мере необходимости (позже вы, вероятно, захотите один файл на уровень, так что это хороший старт). Он может быть расширен, чтобы удерживать начальную позицию игрока, но вы также можете создать специальную плитку, например *
для начальной позиции, и E
для противника в описании уровня.
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's 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()
Я также создал игру, в которой у меня есть игровое меню, меню уровней, загрузочная часть и игровая часть.
То, как я это делал, было в основном игровом цикле,
Я прошел через кучу высказываний elif, чтобы определить, что такое «режим» игры, и делает соответствующие действия.
Казалось, он работает очень хорошо, и я предлагаю вам попробовать то же самое.
Я понимаю, что My Code очень длинный, но если вы перейдете к тому, где он говорит #game loop
(используйте ctrl + f, чтобы найти его), вы можете увидеть elifs для определения режима. Надеюсь это поможет.
#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.bbottomrightself.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.centerxwall.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.centerywall.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.centerywall.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.topwall.bottom and (self.centerxwall.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.leftwall.right and (self.centerywall.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.centerxwall.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)