Python Pygame 入门教程:从零学会创建窗口、绘图和游戏交互

Python Pygame 入门教程:从零学会创建窗口、绘图和游戏交互

很多人在学完 Python 基础语法之后,都会自然进入下一个问题:能不能用 Python 做点"看得见、动得起来、还能交互"的东西?

如果你想做 2D 小游戏、可视化练手项目、编程教学 demo,或者只是想快速理解"图形界面 + 实时交互 + 事件循环"这一套开发思路,那么 Pygame 是一个非常适合新手入门的库。

它的特点很直接:

  • 安装简单,上手门槛不高
  • 可以快速创建窗口并实时刷新画面
  • 支持键盘、鼠标、声音、图片、文字等常见游戏元素
  • 很适合用来学习游戏循环、碰撞检测、动画更新这些基础概念

这篇文章不打算只停留在"弹一个窗口"层面,而是带你通过一个完整示例,快速掌握 Pygame 最常用的能力:

  • 创建窗口和主循环
  • 处理键盘与鼠标事件
  • 绘制矩形、圆形、线条和文本
  • 控制刷新率
  • 使用定时器事件
  • 做一个最基础的碰撞检测小游戏

学完之后,你至少能自己写出一个可以运行、可以交互、可以继续扩展的 Pygame 小程序。

1. Pygame 是什么

Pygame 是 Python 生态里非常经典的 2D 游戏开发库。你可以把它理解成一套"图形、输入、音频、时间控制"的基础工具集合。

它非常适合下面这些场景:

  • 2D 小游戏原型开发
  • 编程教学和游戏化练习
  • 图形交互 demo
  • 需要键盘鼠标操作的可视化小工具

如果你的目标是先把实时渲染、事件驱动、游戏循环这些核心思路学明白,Pygame 很合适。如果以后想继续往更完整的游戏引擎发展,再去接触 GodotUnity 或更复杂的框架也会更顺手。

2. 安装 Pygame

先安装:

bash 复制代码
pip install pygame

安装完成后可以检查版本:

bash 复制代码
python -m pygame --version

如果命令能正常输出版本信息,说明环境已经准备好了。

3. 先理解 Pygame 的核心结构

绝大多数 Pygame 程序,都离不开下面这几个部分:

  1. pygame.init():初始化模块
  2. pygame.display.set_mode(...):创建窗口
  3. while running::主循环,持续刷新画面
  4. pygame.event.get():读取事件,比如关闭窗口、按键、鼠标点击
  5. screen.fill(...)pygame.draw...:绘制内容
  6. pygame.display.flip():把本帧内容显示出来
  7. 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.rectdraw.circledraw.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))

流程通常是:

  1. 创建字体对象
  2. render() 生成文字表面
  3. 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)

当项目变大之后,SpriteGroup 会比手写大量散乱变量更容易维护。

8. 新手学 Pygame 时最容易踩的坑

  • 忘记写事件循环,窗口一打开就假死
  • 忘记每帧重新绘制,导致画面残影或不刷新
  • 没有做帧率控制,导致不同电脑速度不一致
  • 把"事件触发"和"持续按住状态"混在一起写,输入逻辑会变乱
  • 先做太复杂的项目,结果被资源管理、场景切换、类设计拖住

比较稳妥的学习顺序是:

  1. 先学窗口、事件循环、绘图
  2. 再学移动、碰撞、计分
  3. 再补图片、音效、Sprite、动画
  4. 最后再做完整小游戏

9. 总结

Pygame 很适合拿来做 Python 图形交互和 2D 游戏开发入门。它最有价值的地方,不只是"能做小游戏",而是它会逼着你真正理解这几个关键概念:

  • 事件驱动
  • 实时刷新
  • 状态更新
  • 坐标系统
  • 碰撞检测

对新手来说,这些基础一旦打稳,后面无论你继续做 Pygame 项目,还是转向更复杂的图形框架,都会轻松很多。

如果你刚开始学,建议先把这篇文章里的 demo 跑起来,再尝试自己加下面这些功能:

  • 障碍物
  • 多个金币
  • 血量系统
  • 开始菜单
  • 背景图片和音效

从"能运行"到"能扩展",你就算真正入门 Pygame 了。

相关推荐
2401_871492851 小时前
如何在 React Router v6 中正确配置多路由组件显示
jvm·数据库·python
神仙别闹2 小时前
基于Python(Django)+MySQL 实现(Web)SQL智能检测系统的设计与实现
python·mysql·django
甄心爱学习2 小时前
【项目实训】法律文书智能摘要系统4
python·github·个人开发
huzhongqiang3 小时前
Playwright理解与封装
python
zhangchaoxies3 小时前
MySQL触发器能否监控特定用户操作_结合审计功能实现分析
jvm·数据库·python
qq_413502023 小时前
如何解决ORA-12518监听程序无法分配进程_内存耗尽与PGA溢出
jvm·数据库·python
熊猫钓鱼>_>3 小时前
AR游戏的“轻”与“深”:当智能体接管眼镜,游戏逻辑正在发生什么变化?
人工智能·游戏·ai·ar·vr·game·智能体
zhangrelay3 小时前
三分钟云课实践速通--大学物理--python 版
linux·开发语言·python·学习·ubuntu·lubuntu
djjdjdjdjjdj4 小时前
如何用参数解构在函数入口处直接提取对象属性
jvm·数据库·python