成品游戏已部署至GitHub

fufu-eat-rice

目录

  1. 前言:什么是Pygame?
  2. 环境准备:安装Pygame
  3. 第一步:创建第一个游戏窗口
  4. 第二步:游戏的核心——主循环(Game Loop)
  5. 第三步:创建玩家(Sprite)并让它动起来
  6. 第四步:添加敌人并实现碰撞检测
  7. 第五步:锦上添花——图像、音效与计分
  8. 完整代码与未来展望

1. 前言:什么是Pygame?

Pygame是一组专为编写视频游戏而设计的Python模块。它包含了图形、声音、输入处理等游戏开发所需的功能,非常适合初学者入门2D游戏开发。它简单、跨平台,能让你的游戏创意快速变为现实。

我们将通过制作一个简单的“躲避下落物”游戏,来贯穿学习Pygame的整个流程。

2. 环境准备:安装Pygame

在开始之前,请确保你的电脑已经安装了Python。

打开你的终端(在Windows上是CMD或PowerShell,macOS/Linux上是Terminal),然后运行以下命令来安装Pygame:

Bash

pip install pygame

安装完成后,创建一个项目文件夹,并在其中新建一个Python文件,例如 main.py。我们所有的代码都将写在这个文件中。

3. 第一步:创建第一个游戏窗口

万事开头难,但Pygame的开头非常简单。我们先来创建一个空白的游戏窗口。

Python

# main.py

# 1. 导入并初始化
import pygame
pygame.init()  # 初始化所有pygame模块,必须有!

# 2. 设置窗口尺寸等常量
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_TITLE = "我的第一个Pygame游戏"

# 3. 创建游戏窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption(SCREEN_TITLE)

# 4. 保持窗口运行的循环
running = True
while running:
    # 检查事件
    for event in pygame.event.get():
        # 如果是退出事件,则退出
        if event.type == pygame.QUIT:
            running = False

# 5. 退出Pygame
pygame.quit()

代码解析:

  • pygame.init(): 这是每个Pygame程序的必需品,它会初始化所有导入的Pygame模块。
  • pygame.display.set_mode((width, height)): 创建一个指定尺寸的窗口。这个函数返回一个Surface对象,它代表了窗口的整个区域,我们之后所有的绘制操作都在这个screen对象上进行。
  • pygame.display.set_caption("标题"): 设置窗口顶部的标题。
  • while running: 这是一个游戏主循环,游戏中的所有逻辑都在这个循环里不断地重复执行。
  • pygame.event.get(): 获取当前时刻发生的所有用户事件(如鼠标点击、键盘按下、关闭窗口等)的列表。
  • event.type == pygame.QUIT: 判断事件是否是点击窗口关闭按钮。如果是,我们就将running设为False,以便退出循环。
  • pygame.quit(): init()的反操作,卸载所有Pygame模块,清理资源。

现在运行main.py,你应该能看到一个黑色的、标题为“我的第一个Pygame游戏”的窗口了!

4. 第二步:游戏的核心——主循环(Game Loop)

游戏之所以能动起来,是因为它在以极快的速度(通常是每秒60次)重复地“处理输入 -> 更新状态 -> 绘制画面”。这个过程就在我们的主循环中完成。

让我们完善一下主循环的结构,并加入时间控制

Python

# ... (接上面的代码) ...

# 创建时钟对象
clock = pygame.time.Clock()
FPS = 60  # 游戏的帧率

# 游戏主循环
running = True
while running:
    # 1. 控制游戏帧率
    # clock.tick(FPS)会根据上次调用的时间,插入适当的延迟,确保循环每秒运行次数不超过FPS
    dt = clock.tick(FPS) / 1000.0 # dt是时间增量(秒),用于物理计算

    # 2. 处理事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 3. 更新游戏状态 (目前为空)
    # ...

    # 4. 绘制画面
    screen.fill((0, 0, 0))  # 用黑色填充屏幕 (RGB)

    # 5. 更新显示
    pygame.display.flip()   # 将内存中的画面一次性绘制到屏幕上

pygame.quit()

新增代码解析:

  • pygame.time.Clock(): 创建一个时钟对象,用来帮助我们控制游戏的帧率。
  • clock.tick(FPS): 这是游戏循环中非常重要的一行。它会限制循环的最大速度,让游戏在所有电脑上都以相近的速度运行。
  • screen.fill((R, G, B)): 用指定的颜色填充整个screen Surface。颜色使用RGB元组表示,(0, 0, 0)是黑色。
  • pygame.display.flip(): 将你在screen上绘制的所有东西更新到实际的显示器上。如果你有很多东西要画,flip()会一次性全部展示出来,避免画面闪烁。

5. 第三步:创建玩家(Sprite)并让它动起来

游戏不能没有角色。在Pygame中,游戏里的所有物体(玩家、敌人、子弹等)通常都用精灵(Sprite)来表示。使用pygame.sprite.Sprite类来管理它们会非常方便。

5.1 创建玩家类

Python

# 在 import pygame 下方添加
import random

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()  # 调用父类的初始化方法
        # 创建一个50x50的Surface作为玩家的图像
        self.image = pygame.Surface((50, 50))
        self.image.fill((0, 255, 0))  # 填充为绿色
        # 获取图像的矩形区域 (rect),用于定位
        self.rect = self.image.get_rect()
        # 设置初始位置
        self.rect.centerx = SCREEN_WIDTH // 2
        self.rect.bottom = SCREEN_HEIGHT - 10
        self.speed_x = 0  # 初始水平速度

    def update(self):
        # 保持默认速度为0
        self.speed_x = 0

        # 获取当前所有按键的状态
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] or keys[pygame.K_a]:
            self.speed_x = -5
        if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
            self.speed_x = 5

        # 更新位置
        self.rect.x += self.speed_x

        # 防止玩家移出屏幕
        if self.rect.right > SCREEN_WIDTH:
            self.rect.right = SCREEN_WIDTH
        if self.rect.left < 0:
            self.rect.left = 0

代码解析:

  • class Player(pygame.sprite.Sprite): 我们的玩家类继承自Pygame内置的Sprite类。
  • self.image: 精灵的外观,它必须是一个Surface对象。这里我们先用一个绿色的方块代替。
  • self.rect: 精灵的位置和大小,它是一个pygame.Rect对象。所有移动和碰撞检测都基于rect
  • update(): 这个方法定义了精灵每一帧的行为。我们在这里处理移动逻辑。
  • pygame.key.get_pressed(): 返回一个包含所有按键状态的列表。如果某个键被按下,对应的值就是True。这非常适合处理持续的移动。

5.2 在主循环中使用玩家

Python

# ...

# 创建精灵组和玩家实例
all_sprites = pygame.sprite.Group() # 创建一个精灵组
player = Player()                   # 创建玩家对象
all_sprites.add(player)             # 将玩家添加到精灵组中

# 游戏主循环
running = True
while running:
    # ... (控制帧率和处理事件部分不变) ...

    # 3. 更新游戏状态
    all_sprites.update()  # 调用组里所有sprite的update()方法

    # 4. 绘制画面
    screen.fill((0, 0, 0))
    all_sprites.draw(screen)  # 将组里的所有sprite绘制到screen上

    # 5. 更新显示
    pygame.display.flip()

# ...

新增代码解析:

  • pygame.sprite.Group(): 精灵组是一个容器,可以方便地对多个精灵进行统一管理。
  • all_sprites.add(player): 将player实例添加到组中。
  • all_sprites.update(): 这行代码会自动调用组内每一个精灵的update()方法。我们的player.update()就会在这里被执行。
  • all_sprites.draw(screen): 这行代码会把组内每一个精灵的image绘制到它们各自rect所在的位置上。

现在运行游戏,你将看到一个绿色的方块在窗口底部,并且可以用左右方向键或A/D键来控制它移动了!

6. 第四步:添加敌人并实现碰撞检测

有玩家就得有挑战。我们来创建一些从天而降的敌人方块。

6.1 创建敌人(Mob)类

这个过程和创建Player类非常相似。

Python

# 在 Player 类下方添加
class Mob(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((30, 40))
        self.image.fill((255, 0, 0)) # 红色
        self.rect = self.image.get_rect()
        # 随机出现在屏幕顶部
        self.rect.x = random.randrange(SCREEN_WIDTH - self.rect.width)
        self.rect.y = random.randrange(-100, -40)
        self.speed_y = random.randrange(1, 8) # 随机下落速度

    def update(self):
        self.rect.y += self.speed_y
        # 如果移出屏幕底部,就销毁它
        if self.rect.top > SCREEN_HEIGHT + 10:
            self.kill() # kill()会从它所在的所有组中移除自己

6.2 生成敌人并检测碰撞

我们需要一个新的精灵组来存放敌人,并在游戏循环中不断生成它们。

Python

# ...

# 创建精灵组
all_sprites = pygame.sprite.Group()
mobs = pygame.sprite.Group() # 新建一个专门放敌人的组
player = Player()
all_sprites.add(player)

# 在游戏开始前,先生成8个敌人
for i in range(8):
    m = Mob()
    all_sprites.add(m)
    mobs.add(m)

# 游戏主循环
running = True
while running:
    # ... (控制帧率和处理事件部分不变) ...

    # 3. 更新游戏状态
    all_sprites.update()

    # 碰撞检测
    # pygame.sprite.spritecollide(sprite, group, dokill)
    # 检测 player 是否和 mobs 组中的任何一个sprite发生碰撞
    # 第三个参数 True 表示碰撞后,mobs组中的sprite会被自动删除(kill)
    hits = pygame.sprite.spritecollide(player, mobs, True)
    if hits:
        # 如果发生碰撞,游戏结束
        running = False

    # 每当一个敌人消失,就重新生成一个,保持敌人数量
    while len(mobs) < 8:
        m = Mob()
        all_sprites.add(m)
        mobs.add(m)

    # ... (绘制和更新显示部分不变) ...

新增代码解析:

  • mobs = pygame.sprite.Group(): 创建一个专门用于存放敌人的组。这有助于我们只检测玩家与敌人之间的碰撞,而不是所有精灵之间的碰撞。
  • pygame.sprite.spritecollide(player, mobs, True): 这是Pygame强大的碰撞检测函数。它会检查第一个参数(单个精灵)是否与第二个参数(精灵组)中的任何成员的rect重叠。
    • 它返回一个列表,包含所有与player碰撞的mobs
    • dokill参数(这里是True)意味着被撞到的mob会自动从它所在的所有组中被移除。
  • if hits:: 如果返回的列表不是空的,说明发生了碰撞。

现在,游戏的核心玩法已经成型了!红色的方块会从天而降,碰到它们游戏就会结束。

7. 第五步:锦上添花——图像、音效与计分

纯色方块太单调了,让我们用真实的图片、声音和文字来丰富游戏体验。

7.1 加载图像

首先,你需要准备一些图片资源(例如 player.png, mob.png)和声音文件(bgm.mp3, explosion.wav),并将它们放在项目文件夹中。

修改PlayerMob类,将self.image的创建方式从填充颜色改为加载图片。

Python

# 假设你有一个名为 player.png 的图片
# 在 Player 的 __init__ 方法中修改:
# self.image = pygame.Surface((50, 50))
# self.image.fill((0, 255, 0))
player_img = pygame.image.load("player.png").convert() # 加载图像
self.image = pygame.transform.scale(player_img, (50, 40)) # 缩放图像
self.image.set_colorkey((0, 0, 0)) # 将黑色设置为透明

# 在 Mob 的 __init__ 方法中修改:
# self.image = pygame.Surface((30, 40))
# self.image.fill((255, 0, 0))
mob_img = pygame.image.load("mob.png").convert()
self.image = pygame.transform.scale(mob_img, (30, 40))
self.image.set_colorkey((0, 0, 0))

代码解析:

  • pygame.image.load("path/to/image.png"): 加载图片文件,返回一个Surface
  • .convert(): 转换图像的像素格式,可以极大地提升后续绘制的性能。
  • pygame.transform.scale(image, (width, height)): 调整图像尺寸。
  • set_colorkey((R, G, B)): 设置透明色。如果你的图片背景不是透明的,可以用这个方法指定一种颜色让它变透明(比如PNG图片的黑色背景)。

7.2 添加音效和背景音乐

Python

# ... 在初始化部分 ...
# 初始化音频模块
pygame.mixer.init()

# 加载音效和音乐
explosion_sound = pygame.mixer.Sound("explosion.wav")
pygame.mixer.music.load("bgm.mp3")
pygame.mixer.music.set_volume(0.4) # 设置背景音乐音量

# ... 在主循环之前 ...
pygame.mixer.music.play(loops=-1) # 循环播放背景音乐

# ... 在碰撞检测部分 ...
if hits:
    explosion_sound.play() # 播放爆炸音效
    running = False

代码解析:

  • pygame.mixer.init(): 初始化音频模块。
  • pygame.mixer.Sound("file.wav"): 加载短的音效文件(推荐使用.wav格式)。
  • pygame.mixer.music.load("file.mp3"): 加载长的背景音乐(推荐使用.mp3.ogg)。
  • sound.play(): 播放音效。
  • pygame.mixer.music.play(loops=-1): 播放背景音乐,loops=-1表示无限循环。

7.3 显示分数

Python

# ... 在初始化部分 ...
pygame.font.init() # 初始化字体模块
score = 0

# 定义一个函数来绘制文本
font_name = pygame.font.match_font('arial') # 找一个系统默认字体
def draw_text(surf, text, size, x, y):
    font = pygame.font.Font(font_name, size)
    text_surface = font.render(text, True, (255, 255, 255)) # True表示开启抗锯齿
    text_rect = text_surface.get_rect()
    text_rect.midtop = (x, y)
    surf.blit(text_surface, text_rect)

# ... 在主循环的更新状态部分 ...
# 分数随时间增加
score += 1

# ... 在主循环的绘制画面部分 ...
# ... 在 all_sprites.draw(screen) 之后 ...
draw_text(screen, str(score), 18, SCREEN_WIDTH / 2, 10)

代码解析:

  • pygame.font.Font(font_name, size): 创建一个指定字体和大小的字体对象。
  • font.render(text, antialias, color): 根据文本内容、是否抗锯齿和颜色,生成一个包含文字的新的Surface
  • screen.blit(text_surface, position): 将这个文字Surface绘制到屏幕的指定位置。

8. 完整代码与未来展望

将以上所有部分组合起来,你就得到了一个功能齐全的Pygame小游戏!

# -*- coding: utf-8 -*-

import pygame
import random

# --- 常量定义 ---
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60

# 颜色
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

# --- 玩家精灵 ---
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        # 加载玩家图片,并进行缩放
        # 注意:你需要一个名为 'player.png' 的图片文件
        try:
            player_img = pygame.image.load("player.png").convert()
            self.image = pygame.transform.scale(player_img, (50, 40))
            # 设置透明色键,通常是图片的背景色(这里假设是黑色)
            self.image.set_colorkey(BLACK)
        except pygame.error:
            # 如果图片加载失败,则创建一个绿色的方块作为替代
            print("警告:'player.png' 加载失败,使用默认方块代替。")
            self.image = pygame.Surface((50, 50))
            self.image.fill(GREEN)

        self.rect = self.image.get_rect()
        self.rect.centerx = SCREEN_WIDTH // 2
        self.rect.bottom = SCREEN_HEIGHT - 10
        self.speed_x = 0

    def update(self):
        self.speed_x = 0
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] or keys[pygame.K_a]:
            self.speed_x = -8
        if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
            self.speed_x = 8

        self.rect.x += self.speed_x

        # 边界检查,防止玩家移出屏幕
        if self.rect.right > SCREEN_WIDTH:
            self.rect.right = SCREEN_WIDTH
        if self.rect.left < 0:
            self.rect.left = 0

# --- 敌人精灵 ---
class Mob(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        # 加载敌人图片,并进行缩放
        # 注意:你需要一个名为 'mob.png' 的图片文件
        try:
            mob_img = pygame.image.load("mob.png").convert()
            self.image = pygame.transform.scale(mob_img, (30, 40))
            self.image.set_colorkey(BLACK)
        except pygame.error:
            print("警告:'mob.png' 加载失败,使用默认方块代替。")
            self.image = pygame.Surface((30, 40))
            self.image.fill(RED)

        self.rect = self.image.get_rect()
        self.rect.x = random.randrange(SCREEN_WIDTH - self.rect.width)
        self.rect.y = random.randrange(-100, -40)
        self.speed_y = random.randrange(1, 8)

    def update(self):
        self.rect.y += self.speed_y
        # 如果敌人移出屏幕底部,则销毁
        if self.rect.top > SCREEN_HEIGHT + 10:
            self.kill()

# --- 辅助函数:用于在屏幕上绘制文本 ---
def draw_text(surf, text, size, x, y):
    font = pygame.font.Font(font_name, size)
    text_surface = font.render(text, True, WHITE) # True 表示开启抗锯齿
    text_rect = text_surface.get_rect()
    text_rect.midtop = (x, y)
    surf.blit(text_surface, text_rect)

# --- 游戏初始化 ---
pygame.init()
pygame.mixer.init() # 初始化音频
pygame.font.init() # 初始化字体

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("躲避下落物!")
clock = pygame.time.Clock()
font_name = pygame.font.match_font('arial') # 寻找系统中的'arial'字体

# --- 加载游戏资源 ---
try:
    explosion_sound = pygame.mixer.Sound("explosion.wav")
except pygame.error:
    explosion_sound = None
    print("警告:'explosion.wav' 加载失败,将无碰撞音效。")

try:
    pygame.mixer.music.load("bgm.mp3")
    pygame.mixer.music.set_volume(0.4)
except pygame.error:
    print("警告:'bgm.mp3' 加载失败,将无背景音乐。")

# --- 创建精灵和精灵组 ---
all_sprites = pygame.sprite.Group()
mobs = pygame.sprite.Group()
player = Player()
all_sprites.add(player)

# 生成初始的敌人
for i in range(8):
    m = Mob()
    all_sprites.add(m)
    mobs.add(m)

score = 0
if 'pygame.mixer' in pygame.sys.modules and pygame.mixer.get_init():
    pygame.mixer.music.play(loops=-1) # 循环播放背景音乐

# --- 游戏主循环 ---
running = True
while running:
    # 1. 控制帧率
    clock.tick(FPS)

    # 2. 处理事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 3. 更新游戏状态
    all_sprites.update()

    # 碰撞检测
    hits = pygame.sprite.spritecollide(player, mobs, True) # 碰撞后删除mob
    if hits:
        if explosion_sound:
            explosion_sound.play()
        # 在这里你可以添加延迟或显示“游戏结束”画面,而不是立即退出
        running = False

    # 每当敌人数量少于8时,生成新的敌人
    while len(mobs) < 8:
        m = Mob()
        all_sprites.add(m)
        mobs.add(m)

    # 分数更新(简单地随时间增加)
    score += 1

    # 4. 绘制画面
    screen.fill(BLACK) # 黑色背景
    all_sprites.draw(screen)
    draw_text(screen, "Score: " + str(score), 18, SCREEN_WIDTH / 2, 10)

    # 5. 更新显示
    pygame.display.flip()

# --- 游戏结束 ---
pygame.quit()

如何运行

  1. 将上面的代码保存为 main.py
  2. 确保 player.png, mob.png, bgm.mp3, explosion.wav 文件和 main.py 在同一个文件夹里。
  3. 打开终端,进入该文件夹,然后运行命令: python main.py

基于这个框架,你可以尝试实现你更高级的技术:

  • 状态机管理: 创建一个开始菜单界面和一个游戏结束界面。
  • 粒子系统: 在敌人被击中时创建爆炸的粒子效果。
  • 动画系统: 使用多张图片制作玩家或敌人的动画(精灵表)。
  • 高级UI: 创建可以交互的按钮。

希望这份教程能帮助你开启Pygame游戏开发的旅程!享受创造的乐趣吧!

计算机小白一枚
最后更新于 2025-10-01