成品游戏已部署至GitHub
目录
- 前言:什么是Pygame?
- 环境准备:安装Pygame
- 第一步:创建第一个游戏窗口
- 第二步:游戏的核心——主循环(Game Loop)
- 第三步:创建玩家(Sprite)并让它动起来
- 第四步:添加敌人并实现碰撞检测
- 第五步:锦上添花——图像、音效与计分
- 完整代码与未来展望
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)): 用指定的颜色填充整个screenSurface。颜色使用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),并将它们放在项目文件夹中。
修改Player和Mob类,将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()
如何运行
- 将上面的代码保存为
main.py。 - 确保
player.png,mob.png,bgm.mp3,explosion.wav文件和main.py在同一个文件夹里。 - 打开终端,进入该文件夹,然后运行命令:
python main.py。
基于这个框架,你可以尝试实现你更高级的技术:
- 状态机管理: 创建一个开始菜单界面和一个游戏结束界面。
- 粒子系统: 在敌人被击中时创建爆炸的粒子效果。
- 动画系统: 使用多张图片制作玩家或敌人的动画(精灵表)。
- 高级UI: 创建可以交互的按钮。
希望这份教程能帮助你开启Pygame游戏开发的旅程!享受创造的乐趣吧!
Comments NOTHING