Pygame实战(二)
Flappy Bird 代码
积木 1:最小窗口
新建 main.py:
import pygame
import sys
pygame.init()
screen = pygame.display.set_mode((360, 640))
pygame.display.set_caption("Flappy Bird")
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill((135, 206, 235))
pygame.display.flip()
运行:天蓝色窗口,点 X 关闭。
积木 2:改成面向对象
新建 screen.py:
import pygame
import sys
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((360, 640))
pygame.display.set_caption("Flappy Bird")
self.clock = pygame.time.Clock()
self.running = True
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
def update(self):
pass
def draw(self):
self.screen.fill((135, 206, 235))
pygame.display.flip()
def run(self):
while self.running:
self.handle_events()
self.update()
self.draw()
self.clock.tick(60)
pygame.quit()
sys.exit()
main.py 改为:
from screen import Game
if __name__ == "__main__":
game = Game()
game.run()
运行:效果同上。
积木 3:抽出常量到 tool.py
新建 tool.py:
WIDTH = 360
HEIGHT = 640
FPS = 60
SKY_COLOR = (135, 206, 235)
screen.py 改为:
import pygame
import sys
import tool
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((tool.WIDTH, tool.HEIGHT))
pygame.display.set_caption("Flappy Bird")
self.clock = pygame.time.Clock()
self.running = True
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
def update(self):
pass
def draw(self):
self.screen.fill(tool.SKY_COLOR)
pygame.display.flip()
def run(self):
while self.running:
self.handle_events()
self.update()
self.draw()
self.clock.tick(tool.FPS)
pygame.quit()
sys.exit()
运行:效果同上,改参数只动 tool.py。
积木 4:加入小鸟方块
tool.py 追加:
BIRD_X = 80
BIRD_WIDTH = 30
BIRD_HEIGHT = 30
BIRD_COLOR = (255, 255, 0)
新建 sprite.py:
import pygame
import tool
class Bird(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((tool.BIRD_WIDTH, tool.BIRD_HEIGHT))
self.image.fill(tool.BIRD_COLOR)
self.rect = self.image.get_rect()
self.rect.x = tool.BIRD_X
self.rect.y = tool.HEIGHT // 2
screen.py 改为:
import pygame
import sys
import tool
from sprite import Bird
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((tool.WIDTH, tool.HEIGHT))
pygame.display.set_caption("Flappy Bird")
self.clock = pygame.time.Clock()
self.running = True
self.all_sprites = pygame.sprite.Group()
self.bird = Bird()
self.all_sprites.add(self.bird)
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
def update(self):
self.all_sprites.update()
def draw(self):
self.screen.fill(tool.SKY_COLOR)
self.all_sprites.draw(self.screen)
pygame.display.flip()
def run(self):
while self.running:
self.handle_events()
self.update()
self.draw()
self.clock.tick(tool.FPS)
pygame.quit()
sys.exit()
运行:黄色方块停在屏幕左侧中间。
积木 5:小鸟重力与跳跃
tool.py 追加:
GRAVITY = 0.5
JUMP_STRENGTH = -8
sprite.py 的 Bird 类改为:
class Bird(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((tool.BIRD_WIDTH, tool.BIRD_HEIGHT))
self.image.fill(tool.BIRD_COLOR)
self.rect = self.image.get_rect()
self.rect.x = tool.BIRD_X
self.rect.y = tool.HEIGHT // 2
self.velocity = 0
def jump(self):
self.velocity = tool.JUMP_STRENGTH
def update(self):
self.velocity += tool.GRAVITY
self.rect.y += self.velocity
if self.rect.top < 0:
self.rect.top = 0
self.velocity = 0
if self.rect.bottom > tool.HEIGHT:
self.rect.bottom = tool.HEIGHT
return True
return False
def reset(self):
self.rect.y = tool.HEIGHT // 2
self.velocity = 0
screen.py 的 handle_events 里加空格监听:
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.bird.jump()
运行:方块下落,按空格弹起。
积木 6:加入管道
tool.py 追加:
PIPE_WIDTH = 70
PIPE_GAP = 180
PIPE_SPEED = 4
PIPE_SPAWN_INTERVAL = 90
PIPE_COLOR = (0, 200, 0)
sprite.py 追加 Pipe 类:
class Pipe(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, is_top=False):
super().__init__()
self.image = pygame.Surface((width, height))
self.image.fill(tool.PIPE_COLOR)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.is_top = is_top
self.scored = False
def update(self):
self.rect.x -= tool.PIPE_SPEED
def off_screen(self):
return self.rect.right < 0
screen.py 改为:
import pygame
import sys
import random
import tool
from sprite import Bird, Pipe
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((tool.WIDTH, tool.HEIGHT))
pygame.display.set_caption("Flappy Bird")
self.clock = pygame.time.Clock()
self.running = True
self.all_sprites = pygame.sprite.Group()
self.pipe_group = pygame.sprite.Group()
self.bird = Bird()
self.all_sprites.add(self.bird)
self.spawn_timer = 0
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.bird.jump()
def spawn_pipe(self):
gap_y = random.randint(80, tool.HEIGHT - tool.PIPE_GAP - 80)
top = Pipe(tool.WIDTH, 0, tool.PIPE_WIDTH, gap_y, is_top=True)
bottom = Pipe(tool.WIDTH, gap_y + tool.PIPE_GAP,
tool.PIPE_WIDTH, tool.HEIGHT - (gap_y + tool.PIPE_GAP))
self.all_sprites.add(top, bottom)
self.pipe_group.add(top, bottom)
def update(self):
self.spawn_timer += 1
if self.spawn_timer > tool.PIPE_SPAWN_INTERVAL:
self.spawn_timer = 0
self.spawn_pipe()
self.all_sprites.update()
for pipe in list(self.pipe_group):
if pipe.off_screen():
pipe.kill()
def draw(self):
self.screen.fill(tool.SKY_COLOR)
self.all_sprites.draw(self.screen)
pygame.display.flip()
def run(self):
while self.running:
self.handle_events()
self.update()
self.draw()
self.clock.tick(tool.FPS)
pygame.quit()
sys.exit()
运行:绿色管道从右往左移动,间隙随机。
积木 7:碰撞检测与得分
screen.py 加字体、分数,update 里加碰撞和计分:
def __init__(self):
# ...(前面不变)
self.font = pygame.font.Font(None, 40)
self.score = 0
def update(self):
if self.bird.update():
self.bird.reset()
self.score = 0
self.pipe_group.empty()
for s in list(self.all_sprites):
if isinstance(s, Pipe):
s.kill()
return
self.spawn_timer += 1
if self.spawn_timer > tool.PIPE_SPAWN_INTERVAL:
self.spawn_timer = 0
self.spawn_pipe()
self.all_sprites.update()
for pipe in list(self.pipe_group):
if pipe.off_screen():
pipe.kill()
if pygame.sprite.spritecollide(self.bird, self.pipe_group, False):
self.bird.reset()
self.score = 0
self.pipe_group.empty()
for s in list(self.all_sprites):
if isinstance(s, Pipe):
s.kill()
return
for pipe in self.pipe_group:
if pipe.is_top and not pipe.scored and pipe.rect.right < self.bird.rect.left:
pipe.scored = True
self.score += 1
def draw(self):
self.screen.fill(tool.SKY_COLOR)
self.all_sprites.draw(self.screen)
score_text = self.font.render(str(self.score), True, (255, 255, 255))
self.screen.blit(score_text, (tool.WIDTH // 2, 40))
pygame.display.flip()
运行:完整游戏,撞管道或地面后重新开始。
积木 8:加入待机与游戏结束状态
tool.py 追加:
STATE_WAITING = "waiting"
STATE_PLAYING = "playing"
STATE_GAME_OVER = "game_over"
screen.py 改为:
import pygame
import sys
import random
import tool
from sprite import Bird, Pipe
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((tool.WIDTH, tool.HEIGHT))
pygame.display.set_caption("Flappy Bird")
self.clock = pygame.time.Clock()
self.font = pygame.font.Font(None, 40)
self.all_sprites = pygame.sprite.Group()
self.pipe_group = pygame.sprite.Group()
self.bird = Bird()
self.all_sprites.add(self.bird)
self.spawn_timer = 0
self.score = 0
self.state = tool.STATE_WAITING
self.running = True
def reset(self):
self.bird.reset()
self.pipe_group.empty()
for s in list(self.all_sprites):
if isinstance(s, Pipe):
s.kill()
self.spawn_timer = 0
self.score = 0
self.state = tool.STATE_WAITING
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key in (pygame.K_SPACE, pygame.K_w, pygame.K_UP):
self.handle_action()
elif event.type == pygame.MOUSEBUTTONDOWN:
self.handle_action()
def handle_action(self):
if self.state == tool.STATE_WAITING:
self.state = tool.STATE_PLAYING
self.bird.jump()
elif self.state == tool.STATE_PLAYING:
self.bird.jump()
elif self.state == tool.STATE_GAME_OVER:
self.reset()
def spawn_pipe(self):
gap_y = random.randint(80, tool.HEIGHT - tool.PIPE_GAP - 80)
top = Pipe(tool.WIDTH, 0, tool.PIPE_WIDTH, gap_y, is_top=True)
bottom = Pipe(tool.WIDTH, gap_y + tool.PIPE_GAP,
tool.PIPE_WIDTH, tool.HEIGHT - (gap_y + tool.PIPE_GAP))
self.all_sprites.add(top, bottom)
self.pipe_group.add(top, bottom)
def update(self):
if self.state != tool.STATE_PLAYING:
return
if self.bird.update():
self.state = tool.STATE_GAME_OVER
return
self.spawn_timer += 1
if self.spawn_timer > tool.PIPE_SPAWN_INTERVAL:
self.spawn_timer = 0
self.spawn_pipe()
self.all_sprites.update()
for pipe in list(self.pipe_group):
if pipe.off_screen():
pipe.kill()
if pygame.sprite.spritecollide(self.bird, self.pipe_group, False):
self.state = tool.STATE_GAME_OVER
return
for pipe in self.pipe_group:
if pipe.is_top and not pipe.scored and pipe.rect.right < self.bird.rect.left:
pipe.scored = True
self.score += 1
def draw(self):
self.screen.fill(tool.SKY_COLOR)
self.all_sprites.draw(self.screen)
score_text = self.font.render(str(self.score), True, (255, 255, 255))
self.screen.blit(score_text, (tool.WIDTH // 2, 40))
if self.state == tool.STATE_WAITING:
title = self.font.render("Flappy Bird", True, (255, 255, 255))
hint = self.font.render("Press SPACE to Start", True, (255, 255, 255))
self.screen.blit(title, (tool.WIDTH//2 - title.get_width()//2, tool.HEIGHT//2 - 60))
self.screen.blit(hint, (tool.WIDTH//2 - hint.get_width()//2, tool.HEIGHT//2 + 10))
elif self.state == tool.STATE_GAME_OVER:
over = self.font.render("Game Over", True, (255, 0, 0))
hint = self.font.render("Press SPACE to Restart", True, (255, 255, 255))
self.screen.blit(over, (tool.WIDTH//2 - over.get_width()//2, tool.HEIGHT//2 - 60))
self.screen.blit(hint, (tool.WIDTH//2 - hint.get_width()//2, tool.HEIGHT//2 + 10))
pygame.display.flip()
def run(self):
while self.running:
self.handle_events()
self.update()
self.draw()
self.clock.tick(tool.FPS)
pygame.quit()
sys.exit()
运行:游戏先显示"Press SPACE to Start",按空格开始,死亡后回到等待状态。
最终汇总(复制即用)
tool.py
WIDTH = 360
HEIGHT = 640
FPS = 60
SKY_COLOR = (135, 206, 235)
BIRD_COLOR = (255, 255, 0)
PIPE_COLOR = (0, 200, 0)
BIRD_X = 80
BIRD_WIDTH = 30
BIRD_HEIGHT = 30
GRAVITY = 0.5
JUMP_STRENGTH = -8
PIPE_WIDTH = 70
PIPE_GAP = 180
PIPE_SPEED = 4
PIPE_SPAWN_INTERVAL = 90
STATE_WAITING = "waiting"
STATE_PLAYING = "playing"
STATE_GAME_OVER = "game_over"
sprite.py
import pygame
import tool
class Bird(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((tool.BIRD_WIDTH, tool.BIRD_HEIGHT))
self.image.fill(tool.BIRD_COLOR)
self.rect = self.image.get_rect()
self.rect.x = tool.BIRD_X
self.rect.y = tool.HEIGHT // 2
self.velocity = 0
def jump(self):
self.velocity = tool.JUMP_STRENGTH
def update(self):
self.velocity += tool.GRAVITY
self.rect.y += self.velocity
if self.rect.top < 0:
self.rect.top = 0
self.velocity = 0
if self.rect.bottom > tool.HEIGHT:
self.rect.bottom = tool.HEIGHT
return True
return False
def reset(self):
self.rect.y = tool.HEIGHT // 2
self.velocity = 0
class Pipe(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, is_top=False):
super().__init__()
self.image = pygame.Surface((width, height))
self.image.fill(tool.PIPE_COLOR)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.is_top = is_top
self.scored = False
def update(self):
self.rect.x -= tool.PIPE_SPEED
def off_screen(self):
return self.rect.right < 0
screen.py
import pygame
import sys
import random
import tool
from sprite import Bird, Pipe
class Game:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((tool.WIDTH, tool.HEIGHT))
pygame.display.set_caption("Flappy Bird")
self.clock = pygame.time.Clock()
self.font = pygame.font.Font(None, 40)
self.all_sprites = pygame.sprite.Group()
self.pipe_group = pygame.sprite.Group()
self.bird = Bird()
self.all_sprites.add(self.bird)
self.spawn_timer = 0
self.score = 0
self.state = tool.STATE_WAITING
self.running = True
def reset(self):
self.bird.reset()
self.pipe_group.empty()
for s in list(self.all_sprites):
if isinstance(s, Pipe):
s.kill()
self.spawn_timer = 0
self.score = 0
self.state = tool.STATE_WAITING
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key in (pygame.K_SPACE, pygame.K_w, pygame.K_UP):
self.handle_action()
elif event.type == pygame.MOUSEBUTTONDOWN:
self.handle_action()
def handle_action(self):
if self.state == tool.STATE_WAITING:
self.state = tool.STATE_PLAYING
self.bird.jump()
elif self.state == tool.STATE_PLAYING:
self.bird.jump()
elif self.state == tool.STATE_GAME_OVER:
self.reset()
def spawn_pipe(self):
gap_y = random.randint(80, tool.HEIGHT - tool.PIPE_GAP - 80)
top = Pipe(tool.WIDTH, 0, tool.PIPE_WIDTH, gap_y, is_top=True)
bottom = Pipe(tool.WIDTH, gap_y + tool.PIPE_GAP,
tool.PIPE_WIDTH, tool.HEIGHT - (gap_y + tool.PIPE_GAP))
self.all_sprites.add(top, bottom)
self.pipe_group.add(top, bottom)
def update(self):
if self.state != tool.STATE_PLAYING:
return
if self.bird.update():
self.state = tool.STATE_GAME_OVER
return
self.spawn_timer += 1
if self.spawn_timer > tool.PIPE_SPAWN_INTERVAL:
self.spawn_timer = 0
self.spawn_pipe()
self.all_sprites.update()
for pipe in list(self.pipe_group):
if pipe.off_screen():
pipe.kill()
if pygame.sprite.spritecollide(self.bird, self.pipe_group, False):
self.state = tool.STATE_GAME_OVER
return
for pipe in self.pipe_group:
if pipe.is_top and not pipe.scored and pipe.rect.right < self.bird.rect.left:
pipe.scored = True
self.score += 1
def draw(self):
self.screen.fill(tool.SKY_COLOR)
self.all_sprites.draw(self.screen)
score_text = self.font.render(str(self.score), True, (255, 255, 255))
self.screen.blit(score_text, (tool.WIDTH // 2, 40))
if self.state == tool.STATE_WAITING:
title = self.font.render("Flappy Bird", True, (255, 255, 255))
hint = self.font.render("Press SPACE to Start", True, (255, 255, 255))
self.screen.blit(title, (tool.WIDTH//2 - title.get_width()//2, tool.HEIGHT//2 - 60))
self.screen.blit(hint, (tool.WIDTH//2 - hint.get_width()//2, tool.HEIGHT//2 + 10))
elif self.state == tool.STATE_GAME_OVER:
over = self.font.render("Game Over", True, (255, 0, 0))
hint = self.font.render("Press SPACE to Restart", True, (255, 255, 255))
self.screen.blit(over, (tool.WIDTH//2 - over.get_width()//2, tool.HEIGHT//2 - 60))
self.screen.blit(hint, (tool.WIDTH//2 - hint.get_width()//2, tool.HEIGHT//2 + 10))
pygame.display.flip()
def run(self):
while self.running:
self.handle_events()
self.update()
self.draw()
self.clock.tick(tool.FPS)
pygame.quit()
sys.exit()
main.py
from screen import Game
if __name__ == "__main__":
game = Game()
game.run()
打包 exe(可选)
在项目目录打开终端:
pyinstaller -F -w main.py
-F 打包成单文件,-w 隐藏控制台窗口。生成的 exe 在 dist 文件夹里。
浙公网安备 33010602011771号