本模块将引导您完成 Pygame 的安装,并深入理解 Pygame 应用程序的基石——游戏循环、事件处理、Surface 与 Rect 对象、显示控制以及颜色管理。
Pygame 是一组专为编写视频游戏而设计的 Python 模块。它构建在优秀的 SDL (Simple DirectMedia Layer) 库之上,允许您使用 Python 语言创建功能齐全的游戏和多媒体应用程序。SDL 库本身提供了跨平台的底层硬件访问,包括音频、键盘、鼠标、游戏杆和图形硬件(通过 OpenGL/DirectX)。Pygame 通过 Python 封装了这些功能,使得游戏开发更加便捷和高效。
Pygame 的核心优势在于:
虽然 Pygame 主要用于 2D 游戏开发,但通过结合其他库(如 PyOpenGL),也可以实现 3D 图形渲染。然而,本教程将主要聚焦于 Pygame 在 2D 游戏开发中的应用。
在安装 Pygame 之前,您需要确保您的系统中已经安装了 Python。Pygame 通常与 Python 3 的较新版本兼容良好。
检查 Python 版本:
打开您的命令行终端(Windows 上的 CMD 或 PowerShell,macOS/Linux 上的 Terminal),输入:
python --version
# 或者
python3 --version
如果显示了 Python 3.x.x (例如 Python 3.8.5 或更高版本),则表示 Python 已安装。
安装 Python (如果未安装):
如果您的系统中没有 Python,或者版本过低,请访问 Python 官方网站 (https://www.python.org/downloads/
) 下载适合您操作系统的最新稳定版本。在安装过程中,请务必勾选 “Add Python to PATH” (或类似选项),这样可以将 Python添加到系统环境变量中,方便在命令行中直接调用。
Pygame 的安装通常使用 Python 的包管理器 pip 来完成。
打开命令行终端。
使用 pip 安装 Pygame:
输入以下命令并执行:
pip install pygame
或者,如果您同时安装了 Python 2 和 Python 3,或者为了更明确指定,可以使用:
python -m pip install pygame
# 或者
python3 -m pip install pygame
pip 会自动从 PyPI (Python Package Index) 下载 Pygame 包并进行安装。安装过程可能需要一些时间,具体取决于您的网络速度。
内部机制: pip install pygame
命令执行时,pip 会:
pygame
的包。.whl
)。这避免了在用户端编译 SDL 和 Pygame C 代码的复杂过程。site-packages
目录下。验证 Pygame 安装:
安装完成后,您可以通过几种方式验证 Pygame 是否成功安装。
方法一:通过 pip 查看
在命令行输入:
pip show pygame
如果安装成功,会显示 Pygame 的相关信息,如版本号、安装路径等。
方法二:通过 Python 解释器导入
打开 Python 解释器 (在命令行输入 python
或 python3
并回车),然后尝试导入 Pygame:
import pygame # 导入 pygame 模块
print(pygame.ver) # 打印 pygame 的版本号
pygame.quit() # 退出 pygame(虽然这里还没初始化,但好习惯)
exit() # 退出 Python 解释器
如果导入成功并且打印出版本号(例如 ‘2.1.2’),则说明 Pygame 已正确安装。
方法三:运行 Pygame 内置示例 (推荐)
Pygame 自带了一些示例游戏,可以用来测试其功能。在命令行中运行:
python -m pygame.examples.aliens
如果一切正常,您应该会看到一个名为 “Aliens” 的小游戏窗口运行起来。您可以玩一下这个游戏,然后关闭窗口。这不仅验证了 Pygame 的安装,还确认了其图形和声音子系统能正常工作。
其他可用示例包括:
python -m pygame.examples.stars # 星空效果
python -m pygame.examples.chimp # 大猩猩捶打的经典示例
python -m pygame.examples.fonty # 字体渲染示例
尽管 Pygame 的安装通常很顺利,但偶尔也可能遇到问题。以下是一些常见问题及其可能的解决方案:
pip
命令未找到:
Scripts
目录 (其中包含 pip.exe
) 和 Python 的安装目录添加到 PATH 环境变量。--trusted-host
参数信任 PyPI 的主机:pip install pygame --trusted-host pypi.org --trusted-host files.pythonhosted.org
python -m pip install --upgrade pip setuptools
pip install pygame==2.1.0 # 安装指定版本 2.1.0
通常,最新稳定版是最佳选择。如果遇到上述未列出的问题,建议将完整的错误信息复制下来,在搜索引擎(如 Google、Bing)或 Pygame 社区论坛中搜索解决方案。通常,您遇到的问题其他人也可能遇到过。
在开始编写第一个 Pygame 程序之前,理解其核心概念至关重要。这些概念构成了所有 Pygame 应用程序的基础。
任何 Pygame 程序都需要在使用 Pygame 的功能之前进行初始化,并在程序结束时正确退出。
pygame.init()
:
这个函数会初始化所有导入的 Pygame 模块(如 display, font, mixer 等)。它会检查每个模块的依赖关系,并尝试初始化它们。如果某个模块初始化失败,它通常不会引发异常,但可能会返回一个包含成功和失败初始化模块数量的元组。
内部机制: pygame.init()
实际上是迭代调用 Pygame 内部注册的各个子模块的 init()
函数。例如,它会尝试调用 pygame.display.init()
,pygame.font.init()
等。如果某个模块之前被显式地 quit()
过,init()
可能会重新初始化它。这是一个“尽力而为”的函数,旨在简化启动过程。
pygame.quit()
:
这个函数是 pygame.init()
的反操作。它会卸载所有先前已初始化的 Pygame 模块。在 Python 程序退出前调用此函数是一个好习惯,以确保所有 Pygame 资源(如窗口、音频设备)被妥善释放。
内部机制: 与 init()
类似,pygame.quit()
会调用各个子模块的 quit()
函数,例如 pygame.display.quit()
,pygame.font.quit()
。
特定模块的初始化与退出:
除了全局的 pygame.init()
和 pygame.quit()
,您也可以单独初始化或退出特定的 Pygame 模块。例如:
pygame.font.init()
/ pygame.font.quit()
pygame.mixer.init()
/ pygame.mixer.quit()
pygame.init()
和 pygame.quit()
是最简单和推荐的方式。获取模块初始化状态:
可以使用 pygame.
来检查特定模块是否已初始化。例如,pygame.font.get_init()
如果字体模块已初始化则返回 True
,否则返回 False
。
代码示例:基本的 Pygame 程序结构
import pygame # 导入 pygame 库
# 尝试初始化 Pygame 的所有模块
# pygame.init() 返回一个元组 (n_success, n_fail),表示成功和失败初始化的模块数量
# 我们通常不直接使用这个返回值,但了解它是有用的
num_successes, num_failures = pygame.init() # 初始化所有导入的 pygame 模块
print(f"成功初始化 {
num_successes} 个模块, 失败 {
num_failures} 个模块") # 打印初始化结果
# 检查特定模块的初始化状态
if pygame.display.get_init(): # 检查显示模块是否已初始化
print("显示模块已成功初始化!") # 如果已初始化,则打印信息
else:
print("显示模块初始化失败!") # 如果未初始化,则打印信息
# 游戏主逻辑将在这里 (目前为空)
# ...
pygame.quit() # 卸载所有 pygame 模块,释放资源
print("Pygame 已退出") # 打印退出信息
# Python 程序本身的退出 (可选,脚本结束时会自动退出)
# import sys
# sys.exit()
游戏循环是任何交互式游戏或应用程序的核心。它是一个持续运行的循环,负责处理用户输入、更新游戏状态以及在屏幕上渲染游戏画面。一个典型的 Pygame 游戏循环包含以下几个关键阶段:
基本游戏循环结构
import pygame # 导入 pygame 库
# 1. 初始化 Pygame
pygame.init() # 初始化所有 pygame 模块
# 2. 设置显示窗口
screen_width = 800 # 设置屏幕宽度为 800 像素
screen_height = 600 # 设置屏幕高度为 600 像素
# 创建一个屏幕对象 (Surface),尺寸为 screen_width x screen_height
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("我的第一个 Pygame 窗口") # 设置窗口标题
# 3. 游戏循环控制变量
running = True # 布尔变量,控制游戏循环是否继续
# 4. 游戏循环开始
while running: # 当 running 为 True 时,循环继续
# 5. 事件处理层
for event in pygame.event.get(): # 遍历 Pygame 事件队列中的所有事件
if event.type == pygame.QUIT: # 如果事件类型是 QUIT (用户点击了关闭按钮)
running = False # 将 running 设置为 False,以便退出循环
# 6. 游戏逻辑更新层 (目前为空,后续章节会填充)
# 例如: player.update(), enemy.update()
# ...
# 7. 渲染层
screen.fill((0, 0, 0)) # 用黑色填充整个屏幕 (R, G, B)
# 在这里绘制游戏元素 (目前为空,后续章节会填充)
# 例如: screen.blit(player_image, player_rect)
# ...
pygame.display.flip() # 更新整个屏幕的内容到显示器
# 8. 游戏循环结束,退出 Pygame
pygame.quit() # 卸载所有 pygame 模块
# sys.exit() # 确保程序完全退出 (如果需要)
游戏循环的内部机制和重要性:
pygame.event.get()
:此函数从 Pygame 内部的事件队列中获取所有待处理的事件。这是一个关键步骤,因为如果不处理事件,操作系统可能会认为您的应用程序没有响应。特别是 pygame.QUIT
事件,它在用户尝试关闭窗口时产生。screen.fill()
:在每一帧的开始,通常会用背景色填充整个屏幕。这是为了清除上一帧绘制的内容,否则新的绘制会叠加在旧的上面,产生拖影效果。pygame.display.flip()
vs pygame.display.update()
:
pygame.display.flip()
: 更新整个屏幕的内容。如果您的游戏使用了硬件加速和双缓冲(Pygame 默认情况下可能会尝试使用),flip()
会交换前后缓冲区。双缓冲是一种图形技术,后台缓冲区用于绘制新帧,然后一次性交换到前台显示,以避免屏幕撕裂和闪烁。pygame.display.update()
: 更加灵活。如果无参数调用 pygame.display.update()
,其行为类似于 pygame.display.flip()
(在软件显示模式下)。但是,您可以传递一个或多个 Rect
对象(或一个 Rect
列表)给 update()
,这样 Pygame 就只会更新屏幕上指定的这些矩形区域。这对于优化性能非常有用,特别是当屏幕上只有小部分内容发生变化时(称为“脏矩形”渲染)。flip()
通常与 SDL 的 SDL_RenderPresent()
(如果使用 Renderer) 或 SDL_GL_SwapWindow()
(如果使用 OpenGL) 或 SDL_UpdateWindowSurface()
(如果使用软件表面) 相关联。update()
允许更细致的控制,对应于 SDL_UpdateWindowSurfaceRects()
。现代 Pygame (2.x) 更多地依赖于 SDL2 的渲染后端,这使得 flip()
的行为更加一致和高效。事件是 Pygame 中用于与用户和系统交互的核心机制。当用户执行操作(如按下键盘按键、移动鼠标、点击鼠标按钮)或发生系统级事件(如窗口关闭请求、窗口大小改变、游戏手柄连接/断开)时,Pygame 会将这些事件放入一个内部队列中。您的游戏循环需要定期从这个队列中检索并处理这些事件。
事件队列 (Event Queue):Pygame 在内部维护一个先进先出 (FIFO) 的事件队列。pygame.event.get()
函数会从队列中取出所有当前事件,并返回一个包含这些事件对象的列表。取出后,这些事件会从队列中移除。
事件对象 (Event Object):队列中的每个事件都是一个 pygame.event.Event
类型的对象。这个对象至少有两个重要的属性:
type
: 一个整数,表示事件的类型(例如 pygame.KEYDOWN
表示键盘按键按下,pygame.QUIT
表示退出请求)。KEYDOWN
和 KEYUP
事件:
key
: 被按下或释放的键的标识符 (例如 pygame.K_SPACE
代表空格键, pygame.K_a
代表 ‘a’ 键)。mod
: 修饰键的状态 (例如 pygame.KMOD_SHIFT
表示 Shift 键被按下)。unicode
: 按键产生的 unicode 字符 (对于 KEYDOWN
事件)。scancode
: 物理按键的扫描码。MOUSEBUTTONDOWN
和 MOUSEBUTTONUP
事件:
pos
: 一个元组 (x, y)
,表示鼠标事件发生时的光标位置。button
: 整数,表示哪个鼠标按钮被按下或释放 (1=左键, 2=中键, 3=右键, 4=滚轮向上, 5=滚轮向下)。MOUSEMOTION
事件:
pos
: 一个元组 (x, y)
,表示鼠标当前的光标位置。rel
: 一个元组 (dx, dy)
,表示相对于上次 MOUSEMOTION
事件的移动量。buttons
: 一个元组 (left, middle, right)
,表示鼠标按钮的当前状态 (1 表示按下, 0 表示未按下)。常见的事件类型:
Pygame 定义了许多事件类型,所有这些类型都以常量形式存在于 pygame
模块中。一些最常用的事件类型包括:
QUIT
: 用户点击窗口关闭按钮或系统请求关闭。ACTIVEEVENT
: 当 Pygame 显示窗口获得或失去输入焦点,或者窗口被图标化/恢复时。
gain
: 如果为 1,表示获得焦点或取消图标化;如果为 0,表示失去焦点或图标化。state
: 指示事件的具体类型(例如,APPINPUTFOCUS
表示输入焦点)。KEYDOWN
: 键盘按键按下。KEYUP
: 键盘按键释放。MOUSEMOTION
: 鼠标移动。MOUSEBUTTONDOWN
: 鼠标按钮按下。MOUSEBUTTONUP
: 鼠标按钮释放。JOYAXISMOTION
: 游戏手柄的轴移动 (例如摇杆)。JOYBALLMOTION
: 游戏手柄的轨迹球移动。JOYHATMOTION
: 游戏手柄的方向键 (POV hat) 移动。JOYBUTTONDOWN
: 游戏手柄的按钮按下。JOYBUTTONUP
: 游戏手柄的按钮释放。VIDEORESIZE
: 当显示窗口大小被用户改变时 (需要设置窗口为可调整大小 pygame.RESIZABLE
)。
size
: 新的窗口尺寸 (width, height)
。w
, h
: 新的宽度和高度。VIDEOEXPOSE
: 当部分窗口需要重绘时 (通常由操作系统触发)。USEREVENT
: Pygame 允许您定义自定义事件类型。pygame.USEREVENT
是第一个可用的自定义事件 ID,您可以使用 pygame.USEREVENT + n
来创建更多自定义事件。这对于定时器触发的动作或模块间的通信非常有用。事件处理的几种方式:
pygame.event.get()
: 获取所有事件并清空队列。这是最常用的方法。
for event in pygame.event.get(): # 遍历事件列表
if event.type == pygame.QUIT: # 检查是否是退出事件
running = False # 设置 running 为 False 以退出循环
elif event.type == pygame.KEYDOWN: # 检查是否是键盘按下事件
if event.key == pygame.K_ESCAPE: # 如果按下的是 ESC 键
running = False # 也退出循环
pygame.event.poll()
: 从队列中获取单个事件。如果队列为空,它会返回一个类型为 pygame.NOEVENT
的“空”事件对象。这在某些特定情况下可能有用,但通常 pygame.event.get()
更方便。
event = pygame.event.poll() # 获取单个事件
if event.type == pygame.QUIT: # 检查事件类型
running = False # 设置退出标志
# 注意:poll() 只获取一个事件,如果一帧内有多个事件,需要多次调用或在一个循环内调用
pygame.event.wait()
: 从队列中获取单个事件。如果队列为空,它会等待直到有事件发生。这会导致程序阻塞,直到有事件为止,因此在典型的游戏循环中不常用,因为它会阻止游戏逻辑的更新和渲染。但在某些工具或非实时应用中可能有用。
pygame.event.clear(type=None)
: 从事件队列中移除特定类型的事件,或者如果 type
为 None
,则移除所有事件。这在您想忽略某些类型的事件时可能有用,但要小心使用,因为它可能导致错过重要的用户输入。
事件过滤与控制:
pygame.event.set_blocked(type_or_types)
: 阻止指定类型的一个或多个事件进入事件队列。pygame.event.set_allowed(type_or_types)
: 允许先前被阻止的指定类型的一个或多个事件进入事件队列。pygame.event.get_blocked(type)
: 检查特定事件类型是否被阻止。pygame.key.set_repeat(delay, interval)
: 控制按键重复事件。当一个键被按住时,KEYDOWN
事件会首先在 delay
毫秒后产生,然后每隔 interval
毫秒重复产生。调用 pygame.key.set_repeat()
(无参数) 或 pygame.key.set_repeat(0, 0)
可以禁用按键重复。pygame.mouse.set_visible(boolean)
: 设置鼠标光标是否可见。代码示例:扩展事件处理
import pygame # 导入 pygame 模块
import sys # 导入 sys 模块,用于退出程序
# 1. 初始化
pygame.init() # 初始化 pygame
# 2. 显示设置
screen_width = 800 # 屏幕宽度
screen_height = 600 # 屏幕高度
screen = pygame.display.set_mode((screen_width, screen_height)) # 创建屏幕对象
pygame.display.set_caption("事件处理演示") # 设置窗口标题
# 定义颜色 (后续章节会详细讲解)
BLACK = (0, 0, 0) # 定义黑色
WHITE = (255, 255, 255) # 定义白色
RED = (255, 0, 0) # 定义红色
GREEN = (0, 255, 0) # 定义绿色
BLUE = (0, 0, 255) # 定义蓝色
# 游戏状态变量
current_bg_color = WHITE # 当前背景颜色,默认为白色
message = "按 R, G, B 键改变背景颜色, ESC 退出" # 显示的消息
# 字体设置 (后续章节会详细讲解)
try:
font = pygame.font.Font(None, 36) # 使用默认字体,字号 36
# 或者指定字体文件: font = pygame.font.Font("your_font.ttf", 36)
except pygame.error as e: # 捕获可能的字体错误
print(f"加载字体失败: {
e}") # 打印错误信息
font = pygame.font.Font(pygame.font.get_default_font(), 36) # 加载一个绝对默认的字体
# 3. 游戏循环
running = True # 循环控制标志
clock = pygame.time.Clock() # 创建一个 Clock 对象用于控制帧率
while running: # 主循环开始
# 4. 事件处理
for event in pygame.event.get(): # 遍历事件队列
if event.type == pygame.QUIT: # 如果是退出事件
running = False # 设置 running 为 False,准备退出循环
if event.type == pygame.KEYDOWN: # 如果是键盘按键按下事件
print(f"按键按下: key={
event.key}, unicode='{
event.unicode}', mod={
event.mod}, scancode={
event.scancode}") # 打印按键信息
if event.key == pygame.K_ESCAPE: # 如果按下的是 ESC 键
running = False # 设置 running 为 False
elif event.key == pygame.K_r: # 如果按下的是 R 键
current_bg_color = RED # 将背景颜色设置为红色
message = "背景色: 红色" # 更新消息
elif event.key == pygame.K_g: # 如果按下的是 G 键
current_bg_color = GREEN # 将背景颜色设置为绿色
message = "背景色: 绿色" # 更新消息
elif event.key == pygame.K_b: # 如果按下的是 B 键
current_bg_color = BLUE # 将背景颜色设置为蓝色
message = "背景色: 蓝色" # 更新消息
if event.type == pygame.KEYUP: # 如果是键盘按键释放事件
print(f"按键释放: key={
event.key}") # 打印释放的按键信息
if event.type == pygame.MOUSEBUTTONDOWN: # 如果是鼠标按钮按下事件
# event.button: 1-左键, 2-中键, 3-右键, 4-滚轮上, 5-滚轮下
print(f"鼠标按钮按下: button={
event.button}, pos={
event.pos}") # 打印鼠标按钮和位置信息
message = f"鼠标在 {
event.pos} 按下按钮 {
event.button}" # 更新消息
if event.type == pygame.MOUSEBUTTONUP: # 如果是鼠标按钮释放事件
print(f"鼠标按钮释放: button={
event.button}, pos={
event.pos}") # 打印鼠标按钮和位置信息
message = f"鼠标在 {
event.pos} 释放按钮 {
event.button}" # 更新消息
if event.type == pygame.MOUSEMOTION: # 如果是鼠标移动事件
# event.buttons: (左键状态, 中键状态, 右键状态) - 1 为按下, 0 为未按下
# event.pos: 当前鼠标位置 (x, y)
# event.rel: 相对上次移动 (dx, dy)
# 我们通常只在需要拖拽或持续绘制时才频繁处理 MOUSEMOTION
# print(f"鼠标移动: pos={event.pos}, rel={event.rel}, buttons={event.buttons}") # 打印鼠标移动信息 (会产生大量输出)
pass # 暂时不处理详细的鼠标移动,避免过多打印
# 5. 游戏逻辑更新 (本示例中无复杂逻辑)
# ...
# 6. 渲染
screen.fill(current_bg_color) # 用当前背景色填充屏幕
# 渲染文本消息
if font: # 确保字体对象有效
text_surface = font.render(message, True, BLACK if current_bg_color != BLACK else WHITE) # 创建文本 Surface,抗锯齿,颜色为黑色 (如果背景不是黑色) 或白色
text_rect = text_surface.get_rect(center=(screen_width // 2, screen_height // 2)) # 获取文本的 Rect 对象,并将其居中
screen.blit(text_surface, text_rect) # 将文本 Surface 绘制到屏幕上
pygame.display.flip() # 更新整个屏幕
# 7. 控制帧率
clock.tick(60) # 尝试将游戏帧率维持在每秒 60 帧
# 8. 退出
pygame.quit() # 卸载 Pygame 模块
print("Pygame 演示已退出") # 打印退出消息
sys.exit() # 确保程序完全退出
内部机制 - 事件队列和SDL: Pygame 的事件系统是基于 SDL 的事件处理机制。SDL 负责从操作系统捕获底层的输入事件(键盘、鼠标、窗口事件等),并将它们放入一个内部队列。pygame.event.get()
等函数实际上是在查询和操作这个由 SDL 管理的队列。这意味着 Pygame 的事件响应能力和类型直接受到 SDL 支持的限制,但 SDL 本身是一个非常成熟和跨平台的库,因此 Pygame 的事件系统功能强大且可靠。
Surface
是 Pygame 中用于表示图像和屏幕的最核心对象。你可以把 Surface
理解为一个空白的画布或一张图片,你可以在上面绘制形状、加载图片、写入文字等。屏幕本身也是一个特殊的 Surface
对象。
创建 Surface:
pygame.display.set_mode((width, height), flags=0, depth=0)
创建。这是唯一一个直接显示在用户屏幕上的 Surface
。
flags
: 可选参数,用于指定额外的显示特性,如:
pygame.FULLSCREEN
: 创建全屏显示。pygame.DOUBLEBUF
: 启用双缓冲(通常推荐,可以减少闪烁)。现代 Pygame 通常会自动处理好这个。pygame.HWSURFACE
: 尝试在显存中创建 Surface (硬件加速)。pygame.SRCALPHA
: 表示 Surface 可以拥有每像素的 alpha 透明度。这对于创建带有透明背景的图像(如 PNG)或进行 alpha 混合非常重要。pygame.RESIZABLE
: 创建一个用户可以调整大小的窗口。depth
: 像素的色深(位数)。通常让 Pygame 自动选择最佳色深 (设为 0)。pygame.Surface((width, height), flags=0, depth=0)
或从 pygame.image.load("filename.png")
加载图像来创建。这些 Surface
存在于内存中,需要通过“blit”操作绘制到其他 Surface
(通常是屏幕 Surface
) 上才能显示。
flags
: 同样可以使用 SRCALPHA
来创建带 alpha 通道的 Surface。depth
: 可以指定色深,或者从另一个 Surface
(如屏幕 Surface
) 获取以确保格式兼容。Surface 的属性与方法:
get_width()
: 返回 Surface 的宽度 (像素)。get_height()
: 返回 Surface 的高度 (像素)。get_size()
: 返回一个元组 (width, height)
。get_rect(**kwargs)
: 返回一个覆盖整个 Surface 的 pygame.Rect
对象。这个 Rect
对象的 topleft
默认为 (0, 0)
,但可以通过关键字参数 (如 center=(x,y)
, topright=(x,y)
) 来改变其位置。这非常有用,因为 Rect
对象常用于定位和碰撞检测。fill(color, rect=None, special_flags=0)
: 用指定的颜色填充 Surface。可以提供一个可选的 rect
参数来只填充 Surface 的一部分。special_flags
可用于混合模式 (如 pygame.BLEND_ADD
)。blit(source_surface, dest_pos, area=None, special_flags=0)
: 这是将一个 Surface
(源 source_surface
) 绘制(“blit”是 Block Image Transfer 的缩写)到另一个 Surface
(目标,即调用此方法的 Surface) 上的核心方法。
source_surface
: 要绘制的源图像。dest_pos
: 一个 (x, y)
坐标元组,表示源 Surface
的左上角在目标 Surface
上的绘制位置。也可以是一个 Rect
,此时会使用 Rect
的 topleft
坐标。area
: 一个可选的 Rect
对象,指定源 Surface
中的哪个部分被绘制。如果为 None
,则绘制整个源 Surface
。这对于雪碧图动画非常有用。special_flags
: 可用于混合模式,如 pygame.BLEND_RGBA_ADD
, pygame.BLEND_RGBA_MULT
等,实现特殊的视觉效果。convert()
: 返回一个新的 Surface 副本,但其像素格式已转换为与显示器最匹配的格式。这是一个非常重要的优化。当您加载图像(如 PNG, JPG)后,立即对其调用 convert()
(如果图像没有 alpha 透明度) 或 convert_alpha()
(如果图像有 alpha 透明度),可以显著提高后续 blit
操作的速度。因为如果源和目标 Surface 的像素格式不同,Pygame 在每次 blit
时都需要进行昂贵的实时转换。convert_alpha()
: 类似于 convert()
,但会保留原 Surface 的 alpha 透明度信息,并确保新 Surface 的像素格式也支持 alpha。对于需要透明效果的图像(如 PNG 文件),应使用此方法。set_alpha(value, flags=0)
: 设置整个 Surface 的 alpha 透明度(0=完全透明, 255=完全不透明)。这会影响该 Surface 如何与其他 Surface 混合。需要 Surface 本身支持 alpha (例如创建时使用 SRCALPHA
标志,或者是一个 convert_alpha()
后的 Surface)。get_alpha()
: 获取整个 Surface 的 alpha 值。set_colorkey(color, flags=0)
: 设置颜色键透明。指定一种颜色,当此 Surface 被 blit
到其他 Surface 时,所有匹配该颜色的像素都将变为透明。color
可以是 None
来移除颜色键。这是一个较旧的透明度处理方式,对于需要每像素 alpha 的复杂透明效果,使用带有 alpha 通道的 Surface (SRCALPHA
和 convert_alpha()
) 通常更好。get_colorkey()
: 获取当前设置的颜色键。copy()
: 创建并返回该 Surface 的一个全新副本。subsurface(rect)
: 返回一个新的 Surface,它引用了父 Surface 中由 rect
指定的区域的像素数据。对子 Surface 的修改会影响父 Surface,反之亦然。这对于从一个大的雪碧图中提取小块图像非常高效,因为它不复制像素数据。get_at((x, y))
: 返回指定坐标处像素的 Color
对象。set_at((x, y), color)
: 设置指定坐标处像素的颜色。get_at
和 set_at
非常慢,不应在游戏循环中频繁调用以进行大量的像素操作。对于高性能的像素级操作,应使用 pygame.PixelArray
对象或 pygame.surfarray
模块(后者与 NumPy 集成)。lock()
和 unlock()
: 在直接访问 Surface 的像素数据之前(例如通过 PixelArray
或 surfarray
),Surface 可能需要被锁定。这可以防止操作系统在您操作像素时移动或修改 Surface。操作完成后需要解锁。Pygame 通常会自动处理锁定/解锁,但在某些高级情况下需要手动控制。Surface 的内部机制:
Surface
对象的核心是其内部存储的一块内存,其中包含了所有像素的颜色信息。这些数据的组织方式(像素格式)可能因 Surface 的创建方式、色深和是否有 alpha 通道而异。例如,一个 24 位 RGB Surface 的每个像素用 3 字节表示,而一个 32 位 RGBA Surface 的每个像素用 4 字节表示。pygame.SWSURFACE
): 像素数据存储在系统内存 (RAM) 中。所有的绘制操作由 CPU 完成。pygame.HWSURFACE
): Pygame 会尝试将像素数据存储在显存 (VRAM) 中。某些绘制操作(尤其是 blit
)可能会由显卡硬件加速,从而提高性能。然而,并非所有操作都能在硬件 Surface 上得到加速,并且硬件 Surface 的行为可能因显卡驱动和操作系统而异。直接像素访问(如 set_at
)到硬件 Surface 可能非常慢,因为数据可能需要从显存传回系统内存。HWSURFACE
的概念与 SDL 的 Texture 和 Renderer 更相关。pygame.display.set_mode()
创建的屏幕 Surface 通常会尝试利用硬件加速。普通内存中的 Surface
对象通常是软件 Surface。convert()
和 convert_alpha()
的重要性: 当你 blit
一个 Surface 到另一个 Surface 时,如果它们的像素格式不匹配,Pygame 必须在 CPU 上逐像素转换格式。这个过程非常耗时。convert()
将 Surface 转换为与主显示 Surface 最兼容的格式(通常是屏幕的自然格式),这样后续的 blit
操作就变成了直接的内存复制,速度快得多。convert_alpha()
做同样的事情,但保留了 alpha 通道。因此,加载图像后立即转换它们是 Pygame 中的一个关键性能优化技巧。代码示例:使用 Surface 和 Blitting
import pygame # 导入 pygame 模块
import sys # 导入 sys 模块
# 1. 初始化
pygame.init() # 初始化 pygame
# 2. 显示设置
screen_width = 800 # 屏幕宽度
screen_height = 600 # 屏幕高度
# 创建主显示 Surface (屏幕),启用 SRCALPHA 以便更好地处理透明度
screen = pygame.display.set_mode((screen_width, screen_height)) #, pygame.SRCALPHA)
pygame.display.set_caption("Surface 和 Blit 演示") # 设置窗口标题
# 3. 定义颜色
BLACK = (0, 0, 0) # 黑色
WHITE = (255, 255, 255) # 白色
RED = (255, 0, 0) # 红色
GREEN = (0, 255, 100) # 绿色,稍亮的绿色
BLUE = (0, 0, 255) # 蓝色
YELLOW_TRANSPARENT = (255, 255, 0, 128) # 黄色,半透明 (RGBA)
# 4. 创建一些 Surface 对象
# 4.1 一个红色的矩形 Surface
surface_red = pygame.Surface((100, 80)) # 创建一个 100x80 像素的 Surface
surface_red.fill(RED) # 用红色填充这个 Surface
# 4.2 一个带有 Alpha 透明度的绿色圆形 Surface
# 为了使用每像素 alpha,Surface 需要以 SRCALPHA 标志创建
surface_green_circle = pygame.Surface((150, 150), pygame.SRCALPHA) # 创建支持 alpha 的 Surface
surface_green_circle.fill((0,0,0,0)) # 用完全透明填充背景 (R,G,B,A)
# 在这个 surface_green_circle 上绘制一个半透明绿色圆
# pygame.draw.circle(surface, color, center, radius, width=0)
# width=0 表示填充圆形, color 可以是 (R,G,B) 或 (R,G,B,A)
pygame.draw.circle(surface_green_circle, (0, 255, 0, 180), (75, 75), 70) # 绘制一个绿色半透明圆
# 4.3 加载一张图片 (假设你有一张名为 "player.png" 的图片在脚本同目录下)
# 为确保示例可独立运行,我们创建一个简单的 Surface 代替加载图片
try:
# 尝试加载图片 - 如果你有图片,取消下面这行的注释
# player_image_original = pygame.image.load("player.png") # 加载图片
# 为了演示,我们创建一个替代 Surface
player_image_original = pygame.Surface((60, 90), pygame.SRCALPHA) # 创建一个带 alpha 通道的 Surface
player_image_original.fill((0,0,0,0)) # 完全透明背景
pygame.draw.rect(player_image_original, BLUE, (10, 10, 40, 70)) # 在上面画一个蓝色矩形代表玩家
pygame.draw.circle(player_image_original, YELLOW_TRANSPARENT, (30,20), 15) # 画一个黄色半透明头部
# 关键优化:转换图像格式以匹配屏幕格式
# 如果 player.png 有 alpha 通道,使用 convert_alpha()
player_image = player_image_original.convert_alpha() # 转换格式以优化 blitting 速度,保留 alpha
# 如果 player.png 没有 alpha 通道 (例如 JPG),使用 convert()
# player_image = player_image_original.convert()
player_rect = player_image.get_rect(center=(screen_width // 2, screen_height // 2)) # 获取图像的 rect 并居中
except pygame.error as e: # 捕获 pygame 错误,例如文件未找到
print(f"加载或创建玩家图像失败: {
e}") # 打印错误
player_image = None # 将 player_image 设为 None
player_rect = None # 将 player_rect 设为 None
# 4.4 创建一个使用颜色键透明的 Surface
surface_colorkey = pygame.Surface((120, 120)) # 创建 Surface
surface_colorkey.fill(GREEN) # 用绿色填充
pygame.draw.circle(surface_colorkey, BLUE, (60, 60), 50) # 在上面画一个蓝色圆
surface_colorkey.set_colorkey(GREEN) # 将绿色设置为透明色
# 注意: convert() 之后再 set_colorkey() 效果更好,因为它能确保颜色值精确匹配
# surface_colorkey = surface_colorkey.convert()
# surface_colorkey.set_colorkey(GREEN)
# 5. 游戏循环
running = True # 循环控制标志
clock = pygame.time.Clock() # 时钟对象
angle = 0 # 用于旋转的角度
while running: # 主循环
# 6. 事件处理
for event in pygame.event.get(): # 遍历事件
if event.type == pygame.QUIT: # 如果是退出事件
running = False # 结束循环
if event.type == pygame.KEYDOWN: # 如果是按键按下事件
if event.key == pygame.K_ESCAPE: # 如果是 ESC 键
running = False # 结束循环
# 7. 游戏逻辑更新
if player_image and player_rect : # 如果玩家图像和矩形都存在
# 简单移动:让 "玩家" 图像左右来回移动
player_rect.x += 2 # 每次循环向右移动2像素
if player_rect.right > screen_width or player_rect.left < 0: # 如果碰到屏幕边缘
player_rect.x -=2 # 移回
# (这是一个非常基础的移动,可以改进为反向移动等)
angle = (angle + 1) % 360 # 角度递增,用于旋转
# 8. 渲染
screen.fill(BLACK) # 用黑色填充屏幕背景
# Blit 第一个红色 Surface
screen.blit(surface_red, (50, 50)) # 将 surface_red 绘制到屏幕的 (50,50) 位置
# Blit 带有 Alpha 透明度的绿色圆形 Surface
# 它会与背景和其他已绘制的物体正确混合
screen.blit(surface_green_circle, (200, 50)) # 将 surface_green_circle 绘制到 (200,50)
# Blit 玩家图像 (如果存在)
if player_image and player_rect: # 检查图像是否存在
# 演示旋转: pygame.transform.rotate(surface, angle)
# 注意:旋转会创建一个新的 Surface,并且可能改变尺寸
# 频繁旋转未优化的 Surface 会影响性能
# rotated_player_image = pygame.transform.rotate(player_image, angle) # 旋转玩家图像
# rotated_rect = rotated_player_image.get_rect(center=player_rect.center) # 获取旋转后图像的新 rect,保持中心不变
# screen.blit(rotated_player_image, rotated_rect) # 绘制旋转后的图像
screen.blit(player_image, player_rect) # 绘制原始(但已 convert_alpha 的)玩家图像
# Blit 使用颜色键透明的 Surface
screen.blit(surface_colorkey, (50, 200)) # 将 surface_colorkey 绘制到 (50,200)
# 演示 Subsurface: 从屏幕 Surface 创建一个子 Surface
if screen_width > 200 and screen_height > 200: # 确保屏幕够大
sub = screen.subsurface(pygame.Rect(0, 0, 100, 100)) # 获取屏幕左上角 100x100 的区域作为子 Surface
sub.fill(RED) # 用红色填充这个子 Surface 区域 (会直接修改屏幕对应区域)
# 注意:直接修改屏幕的 subsurface 并不总是推荐的做法,通常 subsurface 用于从大图像中提取部分而不复制数据
pygame.display.flip() # 更新整个屏幕显示
# 9. 控制帧率
clock.tick(60) # 保持 60 FPS
# 10. 退出
pygame.quit() # 卸载 pygame
sys.exit() # 退出程序
pygame.Rect
)pygame.Rect
对象用于存储和操作矩形区域。它在 Pygame 中无处不在,主要用于:
Surface
对象在屏幕上的位置和尺寸。Rect
对象并不包含任何像素数据;它仅仅是一组描述矩形位置和大小的数字。
创建 Rect 对象:
my_rect = pygame.Rect(left, top, width, height)
: 这是最直接的创建方式。
left
: 矩形左边缘的 x 坐标。top
: 矩形上边缘的 y 坐标。width
: 矩形的宽度。height
: 矩形的高度。my_rect = pygame.Rect((left, top), (width, height))
: 使用位置元组和尺寸元组。my_rect = pygame.Rect(object)
: 可以传入任何具有 rect
属性的对象(如一个 Sprite
对象)。my_rect = surface.get_rect(**kwargs)
: 从一个 Surface
对象获取其边界矩形。这是非常常用的方法。关键字参数 **kwargs
可以用来直接设置 Rect
的位置属性,例如:
player_rect = player_surface.get_rect(center=(screen_width/2, screen_height/2))
# 获取 Rect 并将其中心置于屏幕中心。enemy_rect = enemy_surface.get_rect(topleft=(100, 50))
# 左上角在 (100, 50)。x
, y
, top
, left
, bottom
, right
, midtop
, midleft
, midbottom
, midright
, centerx
, centery
。Rect 的虚拟属性 (Virtual Attributes):
Rect
对象有很多方便的属性,用于获取和设置其位置和尺寸。当您修改其中一个位置属性(如 centerx
或 right
)时,其他相关的位置属性(如 x
, left
)会自动更新,反之亦然。这使得移动和对齐 Rect
非常方便。
位置属性:
x
, y
: 矩形左上角的 x 和 y 坐标。与 left
和 top
相同。left
: 矩形左边缘的 x 坐标。right
: 矩形右边缘的 x 坐标。 (left + width
)top
: 矩形上边缘的 y 坐标。bottom
: 矩形下边缘的 y 坐标。 (top + height
)centerx
: 矩形中心的 x 坐标。centery
: 矩形中心的 y 坐标。topleft
: 一个元组 (left, top)
。bottomleft
: 一个元组 (left, bottom)
。topright
: 一个元组 (right, top)
。bottomright
: 一个元组 (right, bottom)
。midtop
: 一个元组 (centerx, top)
。midleft
: 一个元组 (left, centery)
。midbottom
: 一个元组 (centerx, bottom)
。midright
: 一个元组 (right, centery)
。center
: 一个元组 (centerx, centery)
。尺寸属性:
w
, h
: 矩形的宽度和高度。与 width
和 height
相同。width
: 矩形的宽度。height
: 矩形的高度。size
: 一个元组 (width, height)
。重要: Rect
的坐标和尺寸值总是整数。如果将浮点数赋给它们,它们会被截断为整数。这对于像素对齐的图形是必要的。如果需要更精确的浮点数位置,您需要自己维护浮点坐标,并在绘制或碰撞检测时将其转换为整数 Rect
坐标。
Rect 的方法:
copy()
: 返回 Rect
的一个新副本。因为 Rect
对象是可变的,有时需要副本以避免意外修改。move(x, y)
: 返回一个新的 Rect
,该 Rect
按给定的 x
和 y
偏移量移动。原 Rect
不变。move_ip(x, y)
: “in-place” 移动。直接修改 Rect
自身的位置,不返回新的 Rect
对象。通常这个更常用,因为它直接更新对象状态。inflate(x, y)
: 返回一个新的 Rect
,其尺寸围绕中心点进行缩放。x
和 y
是添加到宽度和高度上的值(可以是负数以缩小)。原 Rect
不变。inflate_ip(x, y)
: “in-place” 缩放。直接修改 Rect
自身的尺寸。clamp(Rect)
: 返回一个新的 Rect
,该 Rect
被移动到完全包含在传入的 Rect
参数内部。如果原 Rect
比参数 Rect
大,则新 Rect
会被居中并裁剪。clamp_ip(Rect)
: “in-place” 版本。clip(Rect)
: 返回一个新的 Rect
,表示当前 Rect
与参数 Rect
相交的部分。如果它们不相交,则返回一个宽度或高度为0的 Rect
(通常是 (x, y, 0, 0)
)。union(Rect)
: 返回一个能完全包含当前 Rect
和参数 Rect
的最小新 Rect
。unionall(rect_sequence)
: 返回一个能完全包含当前 Rect
和 rect_sequence
中所有 Rect
的最小新 Rect
。fit(Rect)
: 返回一个新的 Rect
,该 Rect
按比例缩放并移动,以适应参数 Rect
内部,同时保持其宽高比。normalize()
: 如果 Rect
的宽度或高度为负,则修正它(通过调整 left/top
并使 width/height
为正)。colliderect(Rect)
: 如果当前 Rect
与参数 Rect
有任何重叠,则返回 True
,否则返回 False
。这是最常用的矩形碰撞检测方法。collidelist(list_of_rects)
: 检查当前 Rect
是否与 list_of_rects
中的任何一个 Rect
相撞。如果相撞,返回它在列表中的第一个碰撞 Rect
的索引。如果不与任何 Rect
相撞,则返回 -1
。collidelistall(list_of_rects)
: 检查当前 Rect
是否与 list_of_rects
中的任何一个 Rect
相撞。返回一个包含所有碰撞 Rect
在列表中的索引的列表。如果无碰撞,返回空列表。collidepoint((x, y))
或 collidepoint(x, y)
: 如果给定的点 (x, y)
在 Rect
内部(包括边界),则返回 True
,否则返回 False
。contains(Rect)
: 如果参数 Rect
完全被包含在当前 Rect
内部,则返回 True
,否则返回 False
。代码示例:使用 Rect 对象
import pygame # 导入 pygame 模块
pygame.init() # 初始化 pygame (虽然在这个纯 Rect 示例中非必需,但好习惯)
# 1. 创建 Rect 对象
rect1 = pygame.Rect(10, 20, 100, 50) # left=10, top=20, width=100, height=50
print(f"Rect1: {
rect1}") # 打印 rect1 的信息,输出
rect2 = pygame.Rect((50, 60), (150, 70)) # 使用元组创建: ((left, top), (width, height))
print(f"Rect2: left={
rect2.left}, top={
rect2.top}, width={
rect2.width}, height={
rect2.height}") # 访问属性
# 2. 访问和修改 Rect 属性
print(f"Rect1.x = {
rect1.x}, Rect1.y = {
rect1.y}") # 访问 x, y (左上角)
print(f"Rect1.center = {
rect1.center}") # 访问中心点 (centerx, centery)
print(f"Rect1.right = {
rect1.right}, Rect1.bottom = {
rect1.bottom}") # 访问右边缘和下边缘
rect1.centerx = 100 # 修改中心 x 坐标
rect1.bottom = 200 # 修改下边缘 y 坐标
print(f"Rect1 修改后: {
rect1}") # 打印修改后的 rect1,其他相关属性会自动更新
print(f"Rect1 修改后.topleft = {
rect1.topleft}") # 检查 topleft 是否已更新
# 3. Rect 的方法
# 3.1 移动 Rect
rect_orig = pygame.Rect(0, 0, 30, 30) # 创建原始矩形
moved_rect = rect_orig.move(10, 15) # move() 返回一个新 Rect,原 Rect 不变
print(f"Rect_orig (移动前): {
rect_orig}") # 打印原始矩形
print(f"Moved_rect: {
moved_rect}") # 打印移动后的新矩形
rect_orig.move_ip(5, -5) # move_ip() "in-place" 修改原 Rect
print(f"Rect_orig (move_ip后): {
rect_orig}") # 打印就地移动后的原始矩形
# 3.2 缩放 Rect
rect_to_inflate = pygame.Rect(100, 100, 50, 50) # 创建待缩放矩形
inflated_rect = rect_to_inflate.inflate(20, 10) # 宽度增加20 (两边各10), 高度增加10 (两边各5)
print(f"Rect_to_inflate (缩放前): {
rect_to_inflate}") # 打印原始矩形
print(f"Inflated_rect: {
inflated_rect}") # 打印缩放后的新矩形
# 注意 inflate 的参数 x, y 是分别加到 width 和 height 上的总值
# 矩形会从中心向外扩展 (x/2, y/2)
rect_to_inflate.inflate_ip(-10, -20) # "in-place" 缩小
print(f"Rect_to_inflate (inflate_ip后): {
rect_to_inflate}") # 打印就地缩放后的原始矩形
# 3.3 碰撞检测
collider1 = pygame.Rect(0, 0, 50, 50) # 创建碰撞体1
collider2 = pygame.Rect(40, 40, 50, 50) # 创建碰撞体2,与 collider1 部分重叠
collider3 = pygame.Rect(100, 100, 20, 20) # 创建碰撞体3,不与 collider1 重叠
if collider1.colliderect(collider2): # 检查 collider1 和 collider2 是否碰撞
print("Collider1 和 Collider2 发生碰撞!") # 如果碰撞则打印
else:
print("Collider1 和 Collider2 未发生碰撞.") # 如果未碰撞则打印
if collider1.colliderect(collider3): # 检查 collider1 和 collider3 是否碰撞
print("Collider1 和 Collider3 发生碰撞!") # 如果碰撞则打印
else:
print("Collider1 和 Collider3 未发生碰撞.") # 如果未碰撞则打印
point_inside = (25, 25) # 定义一个在 collider1 内部的点
point_outside = (60, 60) # 定义一个在 collider1 外部的点
if collider1.collidepoint(point_inside): # 检查点是否在 collider1 内部
print(f"点 {
point_inside} 在 Collider1 内部.") # 如果在内部则打印
else:
print(f"点 {
point_inside} 不在 Collider1 内部.") # 如果不在内部则打印
if collider1.collidepoint(point_outside): # 检查点是否在 collider1 内部
print(f"点 {
point_outside} 在 Collider1 内部.") # 如果在内部则打印
else:
print(f"点 {
point_outside} 不在 Collider1 内部.") # 如果不在内部则打印
# 3.4 包含检测
container_rect = pygame.Rect(0, 0, 200, 200) # 定义一个容器矩形
contained_rect = pygame.Rect(50, 50, 30, 30) # 定义一个完全在容器内的矩形
partially_contained_rect = pygame.Rect(150, 150, 100, 100) # 定义一个部分在容器内的矩形
if container_rect.contains(contained_rect): # 检查 contained_rect 是否完全在 container_rect 内部
print("Contained_rect 完全在 Container_rect 内部.") # 如果是则打印
else:
print("Contained_rect 不完全在 Container_rect 内部.") # 如果不是则打印
if container_rect.contains(partially_contained_rect): # 检查 partially_contained_rect 是否完全在 container_rect 内部
print("Partially_contained_rect 完全在 Container_rect 内部.") # 如果是则打印
else:
print("Partially_contained_rect 不完全在 Container_rect 内部.") # 如果不是则打印
# 3.5 Union
rect_a = pygame.Rect(0,0,20,20) # 定义矩形A
rect_b = pygame.Rect(30,30,20,20) # 定义矩形B
union_ab = rect_a.union(rect_b) # 计算A和B的并集矩形
print(f"Union of A {
rect_a} and B {
rect_b} is {
union_ab}") # 打印并集矩形
# 4. Rect 与 Surface 结合
# 通常,Surface 的位置由其 Rect 决定
# example_surface = pygame.Surface((50, 50)) # 创建一个 Surface
# example_rect = example_surface.get_rect(center=(300, 200)) # 获取 Surface 的 Rect 并设置其中心位置
# 在游戏循环中绘制时: screen.blit(example_surface, example_rect)
# example_rect.x += 5 # 移动 Rect,从而间接移动 Surface 的绘制位置
pygame.quit() # 退出 pygame
Rect 的整数坐标: 一个非常重要的细节是 Rect
的所有坐标和尺寸值(如 x
, y
, width
, height
, centerx
, bottomright
等)都是整数。如果您将一个浮点数赋给这些属性,Pygame 会自动将其截断(不是四舍五入)为整数。
例如:
my_rect = pygame.Rect(0,0,10,10) # 创建矩形
my_rect.x = 10.7 # 设置 x 坐标为浮点数
print(my_rect.x) # 输出将会是 10
my_rect.centerx = 20.5 # 设置中心x坐标为浮点数
print(my_rect.centerx) # 输出将会是 20 (因为 (left+right)/2 然后取整,或者 left + width/2 取整)
这种行为对于像素级精确的屏幕定位是必需的,因为屏幕像素坐标本身就是整数。然而,如果您在游戏逻辑中需要更平滑的移动或物理计算(例如,速度、加速度是浮点数),您应该在单独的浮点变量中存储精确的位置,然后在每一帧准备绘制或进行碰撞检测之前,将这些浮点位置转换为整数 Rect
坐标。
例如,一个简单的物体类可能这样处理:
class GameObject:
def __init__(self, x_float, y_float, surface):
self.x_float = x_float # 存储精确的浮点 x 坐标
self.y_float = y_float # 存储精确的浮点 y 坐标
self.surface = surface # 物体的图像 Surface
self.rect = self.surface.get_rect() # 获取物体的 Rect
self.update_rect_position() # 初始化 Rect 的位置
def update_rect_position(self):
# 将浮点坐标转换为 Rect 的整数坐标 (例如,更新 topleft)
self.rect.topleft = (int(self.x_float), int(self.y_float)) # 使用 int() 截断
# 或者,如果你想让 Rect 的中心点对应浮点坐标:
# self.rect.center = (int(self.x_float), int(self.y_float))
def move(self, dx_float, dy_float):
self.x_float += dx_float # 更新浮点坐标
self.y_float += dy_float # 更新浮点坐标
self.update_rect_position() # 根据新的浮点坐标更新 Rect
def draw(self, screen_surface):
screen_surface.blit(self.surface, self.rect) # 使用 Rect 进行绘制
这种模式允许您在后台进行平滑的浮点运算,同时确保与 Pygame 的整数坐标系统正确交互。
pygame.display
)pygame.display
模块用于控制 Pygame 程序的显示窗口或屏幕。它是创建可见游戏界面、管理窗口属性以及更新屏幕内容的核心。
初始化显示模式:
pygame.display.set_mode(resolution=(width, height), flags=0, depth=0, display=0, vsync=0)
: 这是最重要的函数,用于创建一个用于显示的 Surface
对象(通常称为屏幕 Surface
)。
resolution
: 一个 (width, height)
元组,指定窗口的尺寸(以像素为单位)。flags
: 可选的附加标志,用于控制显示特性。一些常用的标志包括:
pygame.FULLSCREEN
: 创建一个全屏显示。pygame.RESIZABLE
: 允许用户调整窗口大小。如果设置此标志,当用户调整窗口时,会产生 VIDEORESIZE
事件。pygame.NOFRAME
: 创建一个没有边框和标题栏的窗口。pygame.OPENGL
: 创建一个支持 OpenGL 渲染的窗口(用于 3D 图形)。pygame.HWSURFACE
: (已不推荐直接指定,Pygame 会尝试自动使用)尝试使用硬件加速的 Surface。pygame.DOUBLEBUF
: (已不推荐直接指定,Pygame 会尝试自动使用)启用双缓冲。pygame.SCALED
(Pygame 2.0+): 允许 Pygame 在高 DPI 显示器上进行逻辑大小的缩放,或者当窗口大小与请求的逻辑分辨率不同时进行缩放。depth
: 请求的色深(每个像素的位数)。通常设置为 0
,让 Pygame 自动选择最佳或与当前桌面匹配的色深。display
: (高级) 如果系统有多个显示器,指定使用哪个显示器(索引从 0 开始)。vsync
: (Pygame 2.0.1+) 如果设置为 1
,尝试启用垂直同步 (Vsync)。Vsync 可以减少屏幕撕裂,但可能会引入输入延迟,并将帧率限制为显示器的刷新率。Surface
对象。所有绘制到这个 Surface
上的内容最终会显示出来。set_mode
时,Pygame (通过 SDL) 会与操作系统的窗口管理器交互,请求创建一个具有指定属性的窗口。它还会分配必要的图形资源。如果请求硬件加速或双缓冲,SDL 会尝试配置这些特性。返回的 Surface
是特殊的,因为它的内容可以直接或间接映射到显卡帧缓冲区。更新显示内容:
pygame.display.flip()
: 将整个屏幕 Surface
的内容更新到实际显示器上。如果使用了双缓冲,它会交换前后缓冲区。这是最简单直接的更新方式。pygame.display.update(rectangle_or_rectangles=None)
: 更新屏幕的部分区域。
pygame.display.update()
),它的行为类似于 pygame.display.flip()
(在软件模式下)或更新整个窗口。Rect
对象或一个 Rect
对象列表,则只有这些指定的矩形区域会被更新到屏幕上。这对于优化性能非常有用,称为“脏矩形渲染”(Dirty rect animation),即只重绘屏幕上发生变化的部分。flip()
通常对应于 SDL 的 SDL_RenderPresent()
(使用 Renderer API 时) 或 SDL_GL_SwapWindow()
(使用 OpenGL 时) 或 SDL_UpdateWindowSurface()
(使用旧式 Surface API 时)。它隐含了整个屏幕内容的更新。update(rects)
对应于 SDL 的 SDL_UpdateWindowSurfaceRects()
,允许更精细地控制哪些区域需要从后备存储(屏幕 Surface)复制到实际的显示硬件。在现代 SDL2 中,如果 Pygame 使用了 SDL_Renderer,那么 flip
和 update
的底层行为可能都是基于 Renderer 的呈现,但 update(rects)
仍然可以作为一种提示,告诉渲染器哪些区域“脏了”。获取显示信息:
pygame.display.get_surface()
: 返回当前显示模式下代表屏幕的 Surface
对象。这通常与 set_mode()
返回的 Surface
是同一个对象。pygame.display.Info()
: 返回一个 VideoInfo
对象,其中包含有关当前显示硬件和模式的详细信息,如硬件加速状态、窗口管理器信息、当前分辨率、色深等。info = pygame.display.Info() # 获取 VideoInfo 对象
print(f"硬件加速可用: {
info.hw}") # 检查是否支持硬件 surface
print(f"窗口系统: {
info.wm}") # 检查窗口管理器信息
print(f"当前屏幕宽度: {
info.current_w}") # 获取当前屏幕宽度
print(f"当前屏幕高度: {
info.current_h}") # 获取当前屏幕高度
print(f"Bits per pixel: {
info.bitsize}") # 获取每个像素的位数
print(f"Bytes per pixel: {
info.bytesize}") # 获取每个像素的字节数
pygame.display.get_active()
: 如果显示窗口当前是活动的(即拥有焦点),返回 True
。pygame.display.get_init()
: 如果 pygame.display
模块已初始化,返回 True
。窗口管理:
pygame.display.set_caption(title, icontitle=None)
: 设置窗口的标题栏文本。icontitle
(可选) 用于设置窗口最小化时的图标标题 (并非所有系统都支持)。pygame.display.get_caption()
: 返回一个包含 (title, icontitle)
的元组。pygame.display.set_icon(surface)
: 设置窗口的图标。需要传入一个小的 Surface
对象作为图标图像(通常是 32x32 像素)。图标 Surface
最好带有颜色键透明或 alpha 透明度。pygame.display.iconify()
: 最小化(图标化)游戏窗口。返回 True
如果成功。pygame.display.toggle_fullscreen()
: 在全屏和窗口模式之间切换。pygame.display.get_window_size()
(Pygame 2.0+): 获取当前窗口的实际尺寸。这在窗口可调整大小 (RESIZABLE
) 或使用 SCALED
模式时特别有用,因为它可能与 set_mode
请求的逻辑尺寸不同。pygame.display.get_desktop_sizes()
(Pygame 2.0.1+): 返回一个列表的列表,每个子列表 [width, height]
表示一个可用显示器的桌面尺寸。高级显示模式查询:
pygame.display.list_modes(depth=0, flags=pygame.FULLSCREEN, display=0)
: 返回一个列表,其中包含在特定色深 (depth
) 和标志 (flags
) 下可用的显示分辨率 (width, height)
元组。如果 depth
为 0,则返回当前显示器支持的所有分辨率。如果列表为空,则表示没有可用的模式。如果返回 -1
,则表示任何分辨率都可用(例如在窗口模式下)。这对于在全屏前检查支持的分辨率很有用。pygame.display.mode_ok(size, flags=0, depth=0, display=0)
: 检查给定的尺寸 size=(width, height)
、标志和色深是否是一个有效的显示模式。如果有效,返回最接近该模式的匹配色深。如果无效,返回 0
。代码示例:pygame.display
模块的运用
import pygame # 导入 pygame 模块
import sys # 导入 sys 模块
# 1. 初始化 Pygame
pygame.init() # 初始化所有 pygame 模块
# 2. 查询显示能力
print("查询可用的全屏模式 (默认色深):") # 打印信息
try:
modes = pygame.display.list_modes(0, pygame.FULLSCREEN) # 获取所有可用的全屏模式
if not modes: # 如果没有模式可用
print(" 没有找到可用的全屏模式。") # 打印信息
elif modes == -1: # 如果任何分辨率都可用
print(" 任何分辨率都可以在全屏模式下使用 (可能指窗口化全屏或桌面分辨率)。") # 打印信息
else:
for mode in modes[:5]: # 遍历前5个可用模式 (避免过多输出)
print(f" - {
mode[0]}x{
mode[1]}") # 打印模式的宽度和高度
except pygame.error as e: # 捕获 pygame 错误
print(f"查询 list_modes 失败: {
e}") # 打印错误信息
# 检查一个特定模式是否可行
target_mode = (1024, 768) # 目标模式
if pygame.display.mode_ok(target_mode, pygame.FULLSCREEN): # 检查目标模式是否可用 (全屏)
print(f"模式 {
target_mode} 在全屏下是可行的。") # 打印信息
else:
print(f"模式 {
target_mode} 在全屏下可能不可行或不推荐。") # 打印信息
# 3. 设置显示模式
screen_width = 800 # 屏幕宽度
screen_height = 600 # 屏幕高度
flags = pygame.RESIZABLE # 设置窗口可调整大小的标志
# flags = 0 # 普通窗口
# flags = pygame.FULLSCREEN # 全屏模式
# flags = pygame.NOFRAME # 无边框窗口
try:
screen = pygame.display.set_mode((screen_width, screen_height), flags) # 创建屏幕对象,并设置标志
print(f"成功设置显示模式: {
screen.get_size()} flags={
flags}") # 打印成功信息和屏幕尺寸
except pygame.error as e: # 捕获 pygame 错误
print(f"设置显示模式失败: {
e}") # 打印错误信息
pygame.quit() # 退出 pygame
sys.exit() # 退出程序
# 4. 设置窗口标题和图标
pygame.display.set_caption("Pygame Display 演示", "Display Demo") # 设置窗口标题和图标标题
# 尝试设置图标 (需要一个 Surface 对象)
try:
# 创建一个简单的 32x32 Surface 作为图标
icon_surface = pygame.Surface((32, 32), pygame.SRCALPHA) # 创建带 alpha 通道的 Surface
icon_surface.fill((0,0,0,0)) # 用完全透明填充
pygame.draw.circle(icon_surface, (255, 0, 0, 200), (16, 16), 14, 0) # 在上面画一个红色半透明圆
pygame.draw.circle(icon_surface, (255, 255, 255, 255), (16, 16), 8, 0) # 在上面画一个白色实心小圆
pygame.display.set_icon(icon_surface) # 设置窗口图标
print("窗口图标已设置。") # 打印信息
except pygame.error as e: # 捕获 pygame 错误
print(f"设置图标失败: {
e}") # 打印错误信息
except ImportError: # 有些环境可能缺少图像处理后端导致 Surface 创建失败
print("创建图标 Surface 失败,可能缺少依赖。") # 打印信息
# 5. 获取显示信息
display_info = pygame.display.Info() # 获取显示信息对象
print("\n--- 显示信息 ---") # 打印标题
print(f" 硬件加速 (HWSURFACE): {
'是' if display_info.hw else '否'}") # 打印硬件加速信息
print(f" 窗口管理器信息可用: {
'是' if display_info.wm else '否'}") # 打印窗口管理器信息
print(f" 视频内存 (MB,如果可用): {
display_info.video_mem if hasattr(display_info, 'video_mem') else 'N/A'}") # 打印视频内存信息
print(f" 当前窗口尺寸: {
pygame.display.get_window_size() if hasattr(pygame.display, 'get_window_size') else screen.get_size()}") # 打印当前窗口尺寸
print(f" 屏幕 Surface: {
pygame.display.get_surface()}") # 打印屏幕 Surface 对象
print("------------------\n") # 打印分隔线
# 6. 游戏循环
running = True # 循环控制标志
clock = pygame.time.Clock() # 时钟对象
bg_color_cycle = [(255,0,0), (0,255,0), (0,0,255)] # 背景颜色循环列表
color_index = 0 # 当前颜色索引
frame_count = 0 # 帧计数器
rect_to_update_1 = pygame.Rect(50, 50, 100, 100) # 定义第一个更新区域
rect_to_update_2 = pygame.Rect(200, 150, 150, 80) # 定义第二个更新区域
dirty_rects = [] # 用于存储脏矩形的列表
while running: # 主循环
dirty_rects.clear() # 每帧开始前清空脏矩形列表
# 事件处理
for event in pygame.event.get(): # 遍历事件
if event.type == pygame.QUIT: # 如果是退出事件
running = False # 结束循环
if event.type == pygame.KEYDOWN: # 如果是按键按下事件
if event.key == pygame.K_ESCAPE: # 如果是 ESC 键
running = False # 结束循环
if event.key == pygame.K_f: # 如果是 F 键
pygame.display.toggle_fullscreen() # 切换全屏/窗口模式
print(f"切换全屏状态。当前窗口尺寸: {
pygame.display.get_window_size() if hasattr(pygame.display, 'get_window_size') else screen.get_size()}") # 打印信息
if event.key == pygame.K_i: # 如果是 I 键
if pygame.display.get_active(): # 检查窗口是否活动
pygame.display.iconify() # 最小化窗口
print("窗口已最小化 (如果系统支持)。") # 打印信息
if event.type == pygame.VIDEORESIZE: # 如果是窗口大小调整事件 (仅当设置 RESIZABLE 标志时)
screen_width, screen_height = event.size # 获取新的窗口尺寸
screen = pygame.display.set_mode((screen_width, screen_height), flags) # 重新设置显示模式以适应新尺寸
print(f"窗口大小已调整为: {
event.size}, 新的 screen surface: {
screen}") # 打印信息
# 当窗口大小改变时,整个屏幕都需要重绘
dirty_rects.append(screen.get_rect()) # 将整个屏幕区域添加到脏矩形列表
# 游戏逻辑更新
frame_count += 1 # 帧计数器加1
if frame_count % 60 == 0: # 每 60 帧 (约1秒) 切换一次背景颜色
color_index = (color_index + 1) % len(bg_color_cycle) # 更新颜色索引
# 当背景颜色改变时,整个屏幕都需要重绘
dirty_rects.append(screen.get_rect()) # 添加整个屏幕到脏矩形
# 渲染
current_bg_color = bg_color_cycle[color_index] # 获取当前背景颜色
screen.fill(current_bg_color) # 用当前背景色填充屏幕
# 绘制一些东西来演示 update()
pygame.draw.rect(screen, (255, 255, 0), rect_to_update_1) # 在区域1绘制黄色矩形
dirty_rects.append(rect_to_update_1.copy()) # 将区域1添加到脏矩形列表 (用copy避免后续修改影响)
pygame.draw.ellipse(screen, (255, 0, 255), rect_to_update_2) # 在区域2绘制品红色椭圆
dirty_rects.append(rect_to_update_2.copy()) # 将区域2添加到脏矩形列表
# 更新显示
if frame_count % 120 < 60 : # 每两秒中的第一秒使用 flip()
pygame.display.flip() # 更新整个屏幕
if frame_count % 120 == 1: print("使用 pygame.display.flip()") # 首次使用时打印信息
else: # 每两秒中的第二秒使用 update() 和脏矩形
# 过滤掉不在屏幕内的脏矩形,并合并重叠的脏矩形(简化版,实际合并更复杂)
valid_dirty_rects = [r for r in dirty_rects if screen.get_rect().contains(r) or screen.get_rect().colliderect(r)]
if valid_dirty_rects: # 如果有有效的脏矩形
pygame.display.update(valid_dirty_rects) # 只更新脏矩形区域
else: # 如果没有特定脏矩形(例如窗口大小调整后整个屏幕是脏的)
pygame.display.update() # 更新整个屏幕作为回退
if frame_count % 120 == 61: print(f"使用 pygame.display.update() 更新区域: {
valid_dirty_rects}") # 首次使用时打印信息
# 控制帧率
clock.tick(60) # 保持 60 FPS
# 7. 退出
pygame.quit() # 卸载 pygame 模块
print("Pygame display 演示已退出。") # 打印退出信息
sys.exit() # 退出程序
pygame.Color
)在 Pygame 中,颜色通常由代表红 ®、绿 (G)、蓝 (B) 三原色分量的数值来定义。每个分量的范围通常是 0 到 255。有时还会包含第四个分量 Alpha (A),用于表示透明度(0 表示完全透明,255 表示完全不透明)。
Pygame 可以通过多种方式处理颜色:
(R, G, B)
,例如 (255, 0, 0)
代表纯红色。(R, G, B, A)
,例如 (0, 255, 0, 128)
代表半透明的绿色。pygame.Color
对象: 一个专门用于表示和操作颜色的对象。它提供了更丰富的功能和便利性。"red"
, "blue"
, "lightgreen"
。"#FF0000"
(红色), "0x00FF00"
(绿色)。Surface.map_rgb()
返回的)。pygame.Color
对象:
pygame.Color
是 pygame.color.Color
的别名。
创建 Color
对象:
color_obj = pygame.Color(R, G, B, A=255)
: 例如 pygame.Color(255, 0, 0)
或 pygame.Color(0, 255, 0, 100)
。color_obj = pygame.Color("colorname")
: 例如 pygame.Color("magenta")
。color_obj = pygame.Color("#RRGGBBAA")
或 pygame.Color("#RRGGBB")
: 例如 pygame.Color("#FF00FF")
(洋红色), pygame.Color("#00FF0080")
(半透明绿色)。color_obj = pygame.Color(packed_int)
: 使用整数表示。Color
对象的属性:
可以直接访问或修改其 RGBA 分量:
r
: 红色分量 (0-255)。g
: 绿色分量 (0-255)。b
: 蓝色分量 (0-255)。a
: Alpha 透明度分量 (0-255)。my_color = pygame.Color("dodgerblue") # 创建一个名为 "dodgerblue" 的颜色对象
print(f"初始颜色: R={
my_color.r}, G={
my_color.g}, B={
my_color.b}, A={
my_color.a}") # 打印颜色分量
my_color.r = 255 # 修改红色分量
my_color.a = 128 # 修改 alpha 分量
print(f"修改后颜色: R={
my_color.r}, G={
my_color.g}, B={
my_color.b}, A={
my_color.a}") # 再次打印
Color
对象的规范化属性:
可以将颜色分量表示为 0.0 到 1.0 之间的浮点数:
normalize()
: 返回一个 (r_float, g_float, b_float, a_float)
元组,其中每个值都在 0.0 到 1.0 之间。norm_rgba = my_color.normalize() # 获取规范化后的 RGBA 元组
print(f"规范化 RGBA: {
norm_rgba}") # 打印规范化后的值
Color
对象的其他表示法:
hsla
: 获取或设置 (h, s, l, a)
元组。h
(0-359 degrees), s
(0-100%), l
(0-100%), a
(0-100%).hsva
: 获取或设置 (h, s, v, a)
元组。h
(0-359 degrees), s
(0-100%), v
(0-100%), a
(0-100%).cmy
: 获取或设置 (c, m, y)
元组。值范围 0.0-1.0。i1i2i3
: 获取或设置 (i1, i2, i3)
元组。值范围不固定。my_color = pygame.Color(255, 128, 64) # 创建一个颜色对象 (橙色)
h, s, l, a = my_color.hsla # 获取 HSLA 值
print(f"HSLA: H={
h:.2f}, S={
s:.2f}%, L={
l:.2f}%, A={
a:.2f}%") # 打印 HSLA 值
my_color.hsla = ( (h + 30) % 360, s, l, a) # 改变色相 (Hue shift)
print(f"改变色相后 RGBA: ({
my_color.r}, {
my_color.g}, {
my_color.b}, {
my_color.a})") # 打印改变色相后的 RGBA 值
Color
对象的方法:
correct_gamma(gamma)
: 返回一个新的 Color
对象,其颜色分量根据给定的 gamma
值进行了校正。lerp(other_color, amount)
(Pygame 2.0+): 返回一个新的 Color
对象,它是当前颜色和 other_color
之间的线性插值。amount
是一个 0.0 到 1.0 之间的浮点数,0.0 表示当前颜色,1.0 表示 other_color
。何时使用 pygame.Color
对象 vs 元组:
BLACK = (0,0,0)
,元组轻量且方便。Pygame 的大多数接受颜色的函数(如 screen.fill()
, pygame.draw.rect()
)都可以直接使用元组。pygame.Color
对象:
颜色常量:
Pygame 没有内置大量的颜色名称常量 (如 pygame.RED
)。您需要自己定义它们,或者使用 pygame.Color("red")
。通常的做法是在代码开头定义常用的颜色元组:
BLACK = (0, 0, 0) # 定义黑色
WHITE = (255, 255, 255) # 定义白色
RED = (255, 0, 0) # 定义红色
GREEN = (0, 255, 0) # 定义绿色
BLUE = (0, 0, 255) # 定义蓝色
代码示例:使用颜色
import pygame # 导入 pygame 模块
import sys # 导入 sys 模块
pygame.init() # 初始化 pygame
screen_width = 600 # 屏幕宽度
screen_height = 400 # 屏幕高度
screen = pygame.display.set_mode((screen_width, screen_height)) # 创建屏幕对象
pygame.display.set_caption("Pygame 颜色演示") # 设置窗口标题
# 1. 定义颜色
# 1.1 使用元组 (R, G, B)
COLOR_BLACK = (0, 0, 0) # 定义黑色
COLOR_WHITE = (255, 255, 255) # 定义白色
COLOR_CRIMSON = (220, 20, 60) # 定义深红色 (Crimson)
# 1.2 使用元组 (R, G, B, A) - 用于支持 alpha 的 Surface
COLOR_SEAGREEN_SEMITRANSPARENT = (46, 139, 87, 150) # 定义海绿色,半透明
# 1.3 使用 pygame.Color 对象
color_obj_gold = pygame.Color("gold") # 从名称创建颜色对象 "gold"
print(f"Gold (从名称): R={
color_obj_gold.r}, G={
color_obj_gold.g}, B={
color_obj_gold.b}, A={
color_obj_gold.a}") # 打印颜色分量
color_obj_hex = pygame.Color("#8A2BE2") # 从十六进制创建颜色对象 (BlueViolet)
print(f"BlueViolet (从HEX): R={
color_obj_hex.r}, G={
color_obj_hex.g}, B={
color_obj_hex.b}, A={
color_obj_hex.a}") # 打印颜色分量
color_obj_rgba = pygame.Color(100, 100, 255, 200) # 从 RGBA 值创建颜色对象
print(f"自定义 RGBA: R={
color_obj_rgba.r}, G={
color_obj_rgba.g}, B={
color_obj_rgba.b}, A={
color_obj_rgba.a}") # 打印颜色分量
# 修改 Color 对象的属性
color_obj_mutable = pygame.Color("lightblue") # 创建可变颜色对象 "lightblue"
original_r = color_obj_mutable.r # 保存原始红色分量
color_obj_mutable.r = 0 # 将红色分量设置为 0
color_obj_mutable.a = 200 # 设置 alpha 值为 200 (更透明)
# 使用 HSLA
color_hsl_demo = pygame.Color("orange") # 创建颜色对象 "orange"
h_orig, s_orig, l_orig, a_orig = color_hsl_demo.hsla # 获取原始 HSLA 值
print(<