Python Pygame 入门教程:从零学会创建窗口、绘图和游戏交互
很多人在学完 Python 基础语法之后,都会自然进入下一个问题:能不能用 Python 做点"看得见、动得起来、还能交互"的东西?
如果你想做 2D 小游戏、可视化练手项目、编程教学 demo,或者只是想快速理解"图形界面 + 实时交互 + 事件循环"这一套开发思路,那么 Pygame 是一个非常适合新手入门的库。
它的特点很直接:
- 安装简单,上手门槛不高
- 可以快速创建窗口并实时刷新画面
- 支持键盘、鼠标、声音、图片、文字等常见游戏元素
- 很适合用来学习游戏循环、碰撞检测、动画更新这些基础概念
这篇文章不打算只停留在"弹一个窗口"层面,而是带你通过一个完整示例,快速掌握 Pygame 最常用的能力:
- 创建窗口和主循环
- 处理键盘与鼠标事件
- 绘制矩形、圆形、线条和文本
- 控制刷新率
- 使用定时器事件
- 做一个最基础的碰撞检测小游戏
学完之后,你至少能自己写出一个可以运行、可以交互、可以继续扩展的 Pygame 小程序。
1. Pygame 是什么
Pygame 是 Python 生态里非常经典的 2D 游戏开发库。你可以把它理解成一套"图形、输入、音频、时间控制"的基础工具集合。
它非常适合下面这些场景:
- 2D 小游戏原型开发
- 编程教学和游戏化练习
- 图形交互 demo
- 需要键盘鼠标操作的可视化小工具
如果你的目标是先把实时渲染、事件驱动、游戏循环这些核心思路学明白,Pygame 很合适。如果以后想继续往更完整的游戏引擎发展,再去接触 Godot、Unity 或更复杂的框架也会更顺手。
2. 安装 Pygame
先安装:
bash
pip install pygame
安装完成后可以检查版本:
bash
python -m pygame --version
如果命令能正常输出版本信息,说明环境已经准备好了。
3. 先理解 Pygame 的核心结构
绝大多数 Pygame 程序,都离不开下面这几个部分:
pygame.init():初始化模块pygame.display.set_mode(...):创建窗口while running::主循环,持续刷新画面pygame.event.get():读取事件,比如关闭窗口、按键、鼠标点击screen.fill(...)和pygame.draw...:绘制内容pygame.display.flip():把本帧内容显示出来clock.tick(FPS):限制刷新帧率
如果你是第一次接触实时图形程序,可以先记住一句话:
Pygame 程序不是"从上到下跑完就结束",而是不断循环:接收输入、更新状态、重新绘制。
4. 最小可运行示例
先看一个最小窗口程序:
python
import pygame
pygame.init()
screen = pygame.display.set_mode((640, 360))
pygame.display.set_caption("My First Pygame Window")
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((30, 30, 45))
pygame.display.flip()
clock.tick(60)
pygame.quit()
这段代码已经覆盖了最核心的入门动作:
- 创建窗口
- 监听关闭事件
- 循环刷新屏幕
- 把帧率控制在
60 FPS
如果你能看懂这个结构,后面只是不断往这个循环里加能力。
5. 通过一个完整 demo 快速上手常用功能
为了让新手真正能上手,我准备了一个完整的可运行示例。这个 demo 会同时演示:
Rect矩形角色draw.rect、draw.circle、draw.line- 字体和文本渲染
- 键盘移动
- 鼠标点击事件
- 自定义定时器事件
colliderect碰撞检测
我已经把示例整理成独立文件:
pygame_quick_start_demo.py
完整代码如下:
python
import random
import pygame
WIDTH = 900
HEIGHT = 600
FPS = 60
PLAYER_SIZE = 56
PLAYER_SPEED = 280
COIN_RADIUS = 14
COIN_EVENT = pygame.USEREVENT + 1
BACKGROUND = (25, 32, 48)
GRID_COLOR = (40, 52, 74)
TEXT_COLOR = (241, 245, 249)
ACCENT = (56, 189, 248)
PLAYER_COLOR = (251, 191, 36)
COIN_COLOR = (52, 211, 153)
PANEL_COLOR = (15, 23, 42)
def random_coin_position() -> pygame.Vector2:
return pygame.Vector2(
random.randint(60, WIDTH - 60),
random.randint(80, HEIGHT - 60),
)
def draw_background(screen: pygame.Surface) -> None:
screen.fill(BACKGROUND)
for x in range(0, WIDTH, 40):
pygame.draw.line(screen, GRID_COLOR, (x, 0), (x, HEIGHT), 1)
for y in range(0, HEIGHT, 40):
pygame.draw.line(screen, GRID_COLOR, (0, y), (WIDTH, y), 1)
def draw_panel(
screen: pygame.Surface,
title_font: pygame.font.Font,
text_font: pygame.font.Font,
score: int,
elapsed_seconds: int,
message: str,
) -> None:
panel = pygame.Rect(16, 16, WIDTH - 32, 84)
pygame.draw.rect(screen, PANEL_COLOR, panel, border_radius=16)
pygame.draw.rect(screen, ACCENT, panel, width=2, border_radius=16)
title_surface = title_font.render("Pygame Quick Start Demo", True, TEXT_COLOR)
info_surface = text_font.render(
f"score={score} time={elapsed_seconds}s fps={FPS}",
True,
TEXT_COLOR,
)
hint_surface = text_font.render(message, True, (191, 219, 254))
screen.blit(title_surface, (32, 28))
screen.blit(info_surface, (32, 58))
screen.blit(hint_surface, (380, 58))
def draw_player(screen: pygame.Surface, player: pygame.Rect) -> None:
pygame.draw.rect(screen, PLAYER_COLOR, player, border_radius=14)
eye_y = player.y + 18
pygame.draw.circle(screen, PANEL_COLOR, (player.x + 16, eye_y), 4)
pygame.draw.circle(screen, PANEL_COLOR, (player.x + 40, eye_y), 4)
pygame.draw.line(
screen,
PANEL_COLOR,
(player.x + 16, player.y + 38),
(player.x + 40, player.y + 38),
3,
)
def draw_coin(screen: pygame.Surface, coin_position: pygame.Vector2) -> pygame.Rect:
coin_rect = pygame.Rect(
int(coin_position.x - COIN_RADIUS),
int(coin_position.y - COIN_RADIUS),
COIN_RADIUS * 2,
COIN_RADIUS * 2,
)
pygame.draw.circle(screen, COIN_COLOR, coin_rect.center, COIN_RADIUS)
pygame.draw.circle(screen, TEXT_COLOR, coin_rect.center, COIN_RADIUS, 2)
return coin_rect
def clamp_player(player: pygame.Rect) -> None:
player.x = max(0, min(player.x, WIDTH - player.width))
player.y = max(100, min(player.y, HEIGHT - player.height))
def main() -> None:
pygame.init()
pygame.display.set_caption("Pygame 快速上手演示")
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
title_font = pygame.font.SysFont("microsoftyaheiui", 28, bold=True)
text_font = pygame.font.SysFont("consolas", 20)
player = pygame.Rect(120, 240, PLAYER_SIZE, PLAYER_SIZE)
coin_position = random_coin_position()
score = 0
message = "方向键/WASD 移动,鼠标左键刷新目标,R 重开,Esc 退出"
start_ticks = pygame.time.get_ticks()
pygame.time.set_timer(COIN_EVENT, 2500)
running = True
while running:
dt = clock.tick(FPS) / 1000
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_r:
player.topleft = (120, 240)
coin_position = random_coin_position()
score = 0
start_ticks = pygame.time.get_ticks()
message = "游戏状态已重置"
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
coin_position = random_coin_position()
message = "鼠标事件触发:目标位置已刷新"
elif event.type == COIN_EVENT:
coin_position = random_coin_position()
message = "定时器事件触发:目标自动换了一个位置"
keys = pygame.key.get_pressed()
move_x = (keys[pygame.K_d] or keys[pygame.K_RIGHT]) - (
keys[pygame.K_a] or keys[pygame.K_LEFT]
)
move_y = (keys[pygame.K_s] or keys[pygame.K_DOWN]) - (
keys[pygame.K_w] or keys[pygame.K_UP]
)
player.x += int(move_x * PLAYER_SPEED * dt)
player.y += int(move_y * PLAYER_SPEED * dt)
clamp_player(player)
coin_rect = pygame.Rect(
int(coin_position.x - COIN_RADIUS),
int(coin_position.y - COIN_RADIUS),
COIN_RADIUS * 2,
COIN_RADIUS * 2,
)
if player.colliderect(coin_rect):
score += 1
coin_position = random_coin_position()
message = f"碰撞检测成功,当前得分:{score}"
elapsed_seconds = (pygame.time.get_ticks() - start_ticks) // 1000
draw_background(screen)
draw_panel(screen, title_font, text_font, score, elapsed_seconds, message)
draw_player(screen, player)
draw_coin(screen, coin_position)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()
6. 这个 demo 里你实际学会了什么
6.1 创建窗口
python
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Pygame 快速上手演示")
这两行负责创建窗口并设置标题。
6.2 事件循环
python
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
所有键盘、鼠标、窗口关闭行为,都会从事件队列里读取。新手最容易漏掉的点,就是一定要持续消费事件,否则窗口会看起来像"卡死"。
6.3 键盘输入
python
keys = pygame.key.get_pressed()
move_x = (keys[pygame.K_d] or keys[pygame.K_RIGHT]) - (
keys[pygame.K_a] or keys[pygame.K_LEFT]
)
这类写法很适合做持续移动。它不是"按一次触发一次",而是"按住就持续生效"。
6.4 鼠标事件
python
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
coin_position = random_coin_position()
这表示监听鼠标左键点击。新手通常会把鼠标事件和键盘事件写在一起处理,这也是很常见的结构。
6.5 绘图
python
pygame.draw.rect(...)
pygame.draw.circle(...)
pygame.draw.line(...)
Pygame 自带了大量基础绘图 API,不依赖图片素材时,用这些原始图形练手最快。
6.6 文本渲染
python
title_font = pygame.font.SysFont("microsoftyaheiui", 28, bold=True)
title_surface = title_font.render("Pygame Quick Start Demo", True, TEXT_COLOR)
screen.blit(title_surface, (32, 28))
流程通常是:
- 创建字体对象
render()生成文字表面blit()到屏幕上
6.7 帧率控制
python
dt = clock.tick(FPS) / 1000
这行代码很重要。它一方面把程序限制在目标帧率附近,另一方面还给出了每一帧经过的时间 dt,可以让移动速度跟机器性能解耦。
6.8 定时器事件
python
pygame.time.set_timer(COIN_EVENT, 2500)
这表示每 2500ms 向事件队列投递一次 COIN_EVENT。做倒计时、刷怪、周期动画时都很常用。
6.9 碰撞检测
python
if player.colliderect(coin_rect):
score += 1
Rect 的碰撞检测是 Pygame 最常用的入门能力之一。很多小游戏最开始都可以先用矩形碰撞做原型。
7. 再补 3 个新手很常用的 API
7.1 加载图片
python
player_image = pygame.image.load("player.png").convert_alpha()
screen.blit(player_image, (100, 100))
如果图片带透明背景,优先用 convert_alpha()。
7.2 播放音效
python
pygame.mixer.init()
click_sound = pygame.mixer.Sound("click.wav")
click_sound.play()
做按钮反馈、吃金币音效、受击提示时很常见。
7.3 使用 Sprite Group
python
all_sprites = pygame.sprite.Group()
all_sprites.add(player_sprite, enemy_sprite)
all_sprites.update()
all_sprites.draw(screen)
当项目变大之后,Sprite 和 Group 会比手写大量散乱变量更容易维护。
8. 新手学 Pygame 时最容易踩的坑
- 忘记写事件循环,窗口一打开就假死
- 忘记每帧重新绘制,导致画面残影或不刷新
- 没有做帧率控制,导致不同电脑速度不一致
- 把"事件触发"和"持续按住状态"混在一起写,输入逻辑会变乱
- 先做太复杂的项目,结果被资源管理、场景切换、类设计拖住
比较稳妥的学习顺序是:
- 先学窗口、事件循环、绘图
- 再学移动、碰撞、计分
- 再补图片、音效、Sprite、动画
- 最后再做完整小游戏
9. 总结
Pygame 很适合拿来做 Python 图形交互和 2D 游戏开发入门。它最有价值的地方,不只是"能做小游戏",而是它会逼着你真正理解这几个关键概念:
- 事件驱动
- 实时刷新
- 状态更新
- 坐标系统
- 碰撞检测
对新手来说,这些基础一旦打稳,后面无论你继续做 Pygame 项目,还是转向更复杂的图形框架,都会轻松很多。
如果你刚开始学,建议先把这篇文章里的 demo 跑起来,再尝试自己加下面这些功能:
- 障碍物
- 多个金币
- 血量系统
- 开始菜单
- 背景图片和音效
从"能运行"到"能扩展",你就算真正入门 Pygame 了。