Python+Pygame 入门:手把手教你做一个推箱子小游戏
作为刚接触编程的大一新生,总觉得代码太抽象?试试用 Pygame 做个推箱子游戏吧!这个经典小游戏逻辑清晰、代码量适中,特别适合新手练手。
我花了两天时间从 0 到 1 实现,过程中踩了不少 "新手专属" 的坑(比如箱子推不动、人物穿墙)。这篇文章会按 "问题拆解" 的思路,带你一步步做出能玩的推箱子,最后还会分享几个 "轻量化拓展" 技巧,让你的游戏更有特色。
一、先搞懂:推箱子的核心逻辑是什么?
新手做游戏最容易犯的错是 "一上来就写代码"。先想清楚推箱子的核心要素:
-
地图元素:
- 墙壁(不能穿过)
- 空地(可以走)
- 箱子(可以被推,只能推到空地或目标点)
- 目标点(箱子推到这里就算完成)
- 玩家(可以上下左右移动,能推箱子)
-
核心规则:
- 玩家只能推箱子时,玩家只能移动到箱子旁边,再向箱子方向推
- 箱子只能推到空地上,不能推到墙上
- 所有箱子推到目标点,游戏胜利
二、准备工作:3 分钟搭好开发环境
1. 安装 Pygame(避坑版)
直接用 pip 安装可能会遇到版本兼容问题,大一新生建议这样做:
bash
# 先确认Python版本(3.8+最佳)
python --version
# 安装指定版本(稳定性好)
pip install pygame==2.1.2
2. 项目结构(别堆成一锅粥)
新手容易把所有代码写在一个文件里,推荐简单分个类:
plaintext
push_box/
├── main.py # 主程序
└── maps.py # 地图数据(方便以后加新关卡)
三、从零开始写代码:按 "元素→逻辑→整合" 的顺序来
第一步:定义游戏元素(用类封装更清晰)
1. 先画个游戏窗口(解决 "窗口闪屏" 问题)
刚开始我直接在主循环里画元素,画面总是闪。后来才知道要加个 "双缓冲"—— 用一个临时画布先画好,再一次性显示到屏幕上。
python
运行
import pygame
import sys
# 初始化Pygame
pygame.init()
# 游戏常量(大写区分,方便修改)
CELL_SIZE = 50 # 每个格子的大小(像素)
SCREEN_WIDTH = 10 * CELL_SIZE # 10列
SCREEN_HEIGHT = 10 * CELL_SIZE # 10行
# 创建窗口和临时画布(解决闪屏)
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("大一新手的推箱子")
canvas = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT)) # 临时画布
# 颜色定义(用RGB值)
COLOR_WALL = (0, 0, 255) # 蓝色墙壁
COLOR_FLOOR = (220, 220, 220) # 灰色地板
COLOR_BOX = (139, 69, 19) # 棕色箱子
COLOR_TARGET = (0, 255, 0) # 绿色目标点
COLOR_PLAYER = (255, 0, 0) # 红色玩家
2. 定义玩家类(解决 "人物穿墙" 问题)
玩家移动时必须判断 "下一步是不是墙",刚开始我没加判断,结果人物能直接穿墙。
python
运行
class Player:
def __init__(self, x, y):
self.x = x # 格子坐标(不是像素)
self.y = y # 格子坐标(不是像素)
def move(self, dx, dy, map_data):
"""
移动玩家
dx: x方向变化(-1左,1右,0不动)
dy: y方向变化(-1上,1下,0不动)
map_data: 地图数据(用于判断是否能移动)
"""
# 计算新位置
new_x = self.x + dx
new_y = self.y + dy
# 1. 先判断新位置是不是墙(map_data[y][x] == 1 表示墙)
if map_data[new_y][new_x] == 1:
return False # 是墙,不能移动
# 2. 如果新位置是箱子,尝试推动箱子
if map_data[new_y][new_x] == 2: # 2表示箱子
# 箱子要移动到的位置
box_new_x = new_x + dx
box_new_y = new_y + dy
# 检查箱子的新位置是否合法(不是墙也不是其他箱子)
if map_data[box_new_y][box_new_x] in (0, 3): # 0是空地,3是目标点
# 推动箱子(更新地图数据)
map_data[new_y][new_x] = 0 if (map_data[new_y][new_x] == 2) else 3
map_data[box_new_y][box_new_x] = 2
else:
return False # 箱子推不动
# 3. 移动玩家
self.x = new_x
self.y = new_y
return True
3. 地图处理(用数字代表不同元素)
用数字矩阵表示地图,比直接画图形更灵活。0 = 空地,1 = 墙,2 = 箱子,3 = 目标点,4 = 玩家初始位置。
python
运行
# maps.py
def get_level(level=1):
"""返回指定关卡的地图数据和玩家初始位置"""
if level == 1:
# 10x10的地图(行x列)
map_data = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 2, 0, 1, 0, 0, 2, 0, 1],
[1, 0, 0, 0, 1, 1, 1, 0, 0, 1],
[1, 1, 0, 0, 0, 2, 0, 0, 1, 1],
[1, 0, 0, 0, 1, 0, 1, 0, 0, 1],
[1, 0, 2, 0, 1, 0, 1, 0, 0, 1],
[1, 1, 1, 0, 1, 0, 1, 1, 0, 1],
[1, 0, 3, 3, 3, 3, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
player_x, player_y = 1, 1 # 玩家初始位置(第2行第2列)
return map_data, player_x, player_y
# 可以在这里加更多关卡...
第二步:实现核心游戏逻辑(重点是碰撞检测)
1. 初始化游戏数据
python
运行
from maps import get_level
# 加载第一关
map_data, player_x, player_y = get_level(1)
player = Player(player_x, player_y)
# 计算目标点数量(用于判断胜利)
target_count = sum(row.count(3) for row in map_data)
2. 判断游戏是否胜利(所有箱子都在目标点上)
python
运行
def check_win(map_data, target_count):
"""检查是否所有目标点都有箱子"""
# 统计"箱子在目标点上"的数量
box_on_target = 0
for y in range(len(map_data)):
for x in range(len(map_data[y])):
# 箱子(2)在目标点(3)上时,我们把它记为2(推动时会处理)
# 这里统计所有在目标点上的箱子
if map_data[y][x] == 2 and original_map[y][x] == 3:
box_on_target += 1
return box_on_target == target_count
# 保存原始地图(用于判断箱子是否在目标点上)
original_map = [row.copy() for row in map_data]
第三步:写主循环(把所有逻辑串起来)
主循环是游戏的 "心脏",负责处理输入、更新状态、绘制画面。
python
运行
# 游戏主循环
clock = pygame.time.Clock()
running = True
win = False
while running:
# 1. 处理事件(按键、关闭窗口等)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 处理按键(只在没胜利时响应)
if event.type == pygame.KEYDOWN and not win:
if event.key == pygame.K_UP:
player.move(0, -1, map_data) # 上移
elif event.key == pygame.K_DOWN:
player.move(0, 1, map_data) # 下移
elif event.key == pygame.K_LEFT:
player.move(-1, 0, map_data) # 左移
elif event.key == pygame.K_RIGHT:
player.move(1, 0, map_data) # 右移
elif event.key == pygame.K_r: # 按R键重新开始
map_data, px, py = get_level(1)
player = Player(px, py)
win = False
# 检查是否胜利
if not win:
win = check_win(map_data, target_count)
# 2. 绘制画面(先画在临时画布上)
canvas.fill(COLOR_FLOOR) # 填充背景
# 绘制地图元素
for y in range(len(map_data)):
for x in range(len(map_data[y])):
# 计算像素坐标(格子坐标×格子大小)
pixel_x = x * CELL_SIZE
pixel_y = y * CELL_SIZE
# 绘制墙壁
if map_data[y][x] == 1:
pygame.draw.rect(canvas, COLOR_WALL,
(pixel_x, pixel_y, CELL_SIZE, CELL_SIZE))
# 绘制目标点(即使上面有箱子也要显示)
if original_map[y][x] == 3:
# 画一个小一点的正方形作为目标点
pygame.draw.rect(canvas, COLOR_TARGET,
(pixel_x+10, pixel_y+10,
CELL_SIZE-20, CELL_SIZE-20))
# 绘制箱子
if map_data[y][x] == 2:
pygame.draw.rect(canvas, COLOR_BOX,
(pixel_x+5, pixel_y+5,
CELL_SIZE-10, CELL_SIZE-10))
# 绘制玩家
pygame.draw.circle(canvas, COLOR_PLAYER,
(player.x*CELL_SIZE + CELL_SIZE//2,
player.y*CELL_SIZE + CELL_SIZE//2),
CELL_SIZE//2 - 5)
# 绘制胜利文字
if win:
font = pygame.font.Font(None, 72)
text = font.render("胜利!按R重来", True, (255, 0, 0))
canvas.blit(text, (SCREEN_WIDTH//2 - text.get_width()//2,
SCREEN_HEIGHT//2 - text.get_height()//2))
# 3. 把临时画布显示到屏幕上(解决闪屏)
screen.blit(canvas, (0, 0))
pygame.display.flip()
# 控制帧率(60帧/秒)
clock.tick(60)
# 退出游戏
pygame.quit()
sys.exit()
四、新手最容易踩的 6 个坑(我全中了)
-
坐标搞混:把格子坐标(x=1,y=1)和像素坐标(x=50,y=50)弄混,导致元素画错位。解决:始终用
像素坐标 = 格子坐标 × 格子大小转换。 -
推动逻辑错误:推箱子时只判断了玩家能不能动,没判断箱子能不能动,导致箱子能穿墙。解决:先算箱子要去的位置,合法才能推。
-
地图复制错误:重置关卡时直接
new_map = old_map,结果改新地图时旧地图也变了。解决:用[row.copy() for row in old_map]深复制。 -
画面闪屏:每帧直接在屏幕上画元素,导致闪屏。解决:用临时画布
Surface先画好,再一次性显示。 -
胜利判断错误:只检查箱子数量,没检查是否在目标点上。解决:保存原始地图,对比箱子位置是否在目标点上。
-
边界没处理:玩家走到地图边缘时会报错。解决:在地图外围加一圈墙(像示例地图那样)。
五、3 个轻量化拓展(让你的游戏更酷)
1. 加个关卡选择(5 分钟搞定)
在maps.py里加几个地图,再用数字键 1、2、3 切换:
python
运行
# 在事件处理里加
if event.key == pygame.K_1:
map_data, px, py = get_level(1)
player = Player(px, py)
win = False
elif event.key == pygame.K_2:
map_data, px, py = get_level(2)
player = Player(px, py)
win = False
2. 加移动音效(1 行代码)
python
运行
# 初始化时加载音效
move_sound = pygame.mixer.Sound("move.wav") # 需要一个wav文件
push_sound = pygame.mixer.Sound("push.wav")
# 玩家移动成功时播放
if player.move(0, -1, map_data):
move_sound.play()
3. 显示步数统计(记录玩家操作)
python
运行
# 初始化时加个步数变量
steps = 0
# 每次移动成功时加1
if player.move(0, -1, map_data):
steps += 1
# 绘制步数
font = pygame.font.Font(None, 36)
text = font.render(f"步数: {steps}", True, (0, 0, 0))
canvas.blit(text, (10, 10))
最后:完整代码和学习建议
作为大一新生,我的体会是:做游戏比刷题库有趣多了!推箱子虽然简单,但包含了 "坐标转换"" 碰撞检测 ""状态判断" 等编程核心思想。刚开始写不出完整代码很正常,先复制我的代码跑起来,再一点点改参数(比如格子大小、颜色),慢慢就有感觉了。
如果这篇文章帮到了你,欢迎在评论区分享你的修改版本~ 祝大家都能在编程中找到乐趣!
更多推荐

所有评论(0)