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.pyBird 类改为:

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.pyhandle_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 文件夹里。

posted on 2026-09-14 00:17  凌晨码字  阅读(3)  评论(0)    收藏  举报