ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python小游戏练手项目:30个经典游戏从入门到进阶

Python小游戏练手项目:30个经典游戏从入门到进阶 1. 从玩一天说起为什么小游戏是Python入门的最优解很多人学Python卡在同一个地方语法书翻了大半本循环、函数、类都认识但打开编辑器就不知道写什么。看教程的时候觉得懂了关掉视频脑子一片空白。这个状态我太熟悉了当年我也是这么过来的。后来我发现一个特别有效的破局方法——用游戏项目驱动学习。原因很简单游戏有即时反馈。你写一行代码屏幕上就多一个方块你改一个参数小球弹跳的高度立刻变了。这种所见即所得的反馈循环是看十遍教程都给不了的。而30个Python小游戏这个思路之所以能让人玩一天核心在于它把学习曲线切成了极小的台阶。每个游戏只涉及两三个新知识点比如贪吃蛇练的是列表操作和坐标更新打砖块练的是碰撞检测和速度向量猜数字练的是随机数和条件判断。你不需要一次性理解所有东西只需要在做一个具体游戏的过程中把那一两个知识点吃透。这篇文章我会从项目选型逻辑、环境搭建、核心游戏拆解、常见坑点、进阶改造几个维度把用Python小游戏练手这件事讲透。不管你是刚学完基础语法的新手还是想找点练手项目巩固的老手都能直接抄作业。提示本文涉及的所有游戏均为经典玩法复现代码基于公开的通用编程教学思路整理不涉及任何具体商业项目或平台。2. 30个游戏怎么选从能跑起来到有点意思的梯度设计2.1 为什么不是随便找30个游戏就行网上搜Python小游戏能搜出几千个结果。但如果你随便挑30个来练大概率会陷入两个极端要么太简单比如纯打印文字的猜数字做三个就腻了要么太难比如带物理引擎的平台跳跃第一个就卡死。一个合理的练手项目梯度应该满足三个条件依赖递减前期尽量只用标准库后期再引入pygame等第三方库知识点递进每个游戏至少引入一个新概念但不重复堆砌可玩性递增从能跑到想玩保持正反馈我按这个逻辑把30个游戏分成了四个梯队你可以根据自己的水平直接从对应梯队切入。2.2 四个梯队的游戏清单与知识点映射第一梯队纯控制台游戏8个这一梯队完全不需要任何第三方库打开Python自带的IDLE就能写。适合刚学完变量、循环、条件判断的人。游戏名称核心知识点难度猜数字random、while循环、input入门石头剪刀布字典、条件分支入门文字冒险函数封装、状态管理入门井字棋二维列表、胜负判断入门21点列表操作、随机抽样入门猜单词字符串处理、集合入门数字华容道列表切片、打乱算法入门迷宫生成递归、深度优先搜索进阶第一梯队里我特别推荐井字棋和数字华容道。井字棋的胜负判断逻辑检查行、列、对角线是很多棋盘类游戏的基础写一遍就忘不掉。数字华容道则能让你真正理解列表的原地修改和切片操作这两个东西在后面处理数据时天天用。第二梯队pygame入门游戏10个从这里开始需要安装pygame。这一梯队的重点是理解游戏循环这个概念——每帧处理输入、更新状态、重绘画面。游戏名称核心知识点难度贪吃蛇坐标队列、碰撞检测入门打砖块矩形碰撞、速度向量入门乒乓球边界反弹、AI对手入门飞机大战精灵组、子弹管理入门俄罗斯方块矩阵旋转、行消除进阶扫雷递归展开、网格渲染进阶五子棋棋盘绘制、连子判断进阶连连看路径查找、图案匹配进阶推箱子状态回退、关卡设计进阶贪吃蛇进阶版多食物、障碍物、加速进阶贪吃蛇是pygame入门的Hello World但很多人写完基础版就停了。我建议一定要做进阶版加入障碍物、加速道具、多食物竞争。这些改动会逼你重新设计数据结构从简单的列表变成更复杂的对象管理。第三梯队带简单AI的游戏7个这一梯队开始引入算法思维游戏本身不难但对手的智能需要你动点脑筋。游戏名称核心知识点难度井字棋AI极小化极大算法进阶五子棋AI评分函数、搜索进阶贪吃蛇AIBFS寻路进阶迷宫寻路A*算法进阶数独求解回溯算法进阶八皇后递归回溯进阶生命游戏元胞自动机进阶这里的极小化极大算法是重点。井字棋AI写完之后你会对搜索树和评估函数有直观理解这是后面学机器学习的基础。贪吃蛇AI的BFS寻路也很有意思看着蛇自己找食物那种成就感比手动玩强十倍。第四梯队创意改造与组合5个最后这一梯队没有固定玩法核心是把前面的东西组合起来。用pygame做一个带菜单的游戏合集给贪吃蛇加上存档和排行榜把井字棋改成网络对战用socket做一个游戏关卡编辑器用PIL库把游戏画面导出成GIF这一梯队的目标不是学新知识而是练工程能力——怎么组织代码、怎么设计接口、怎么处理异常。这些能力在实际工作中比语法重要得多。2.3 选型时最容易踩的坑很多人选游戏只看好不好玩结果选了一个自己根本hold不住的项目。我的建议是先看依赖再看知识点最后看玩法。如果一个游戏需要你同时学三个新库直接跳过。如果一个游戏的知识点你已经很熟了也跳过。最好的选择是依赖你都会装知识点你听过但没用过玩法你觉得有点意思。这个区间学习效率最高。3. 环境搭建别在第一步就卡住3.1 Python版本与pygame安装的实操细节写小游戏Python版本建议用3.8以上。太老的版本有些语法不支持太新的版本比如3.13有些第三方库还没适配。3.10或3.11是比较稳的选择。安装pygame的命令很简单pip install pygame但实际安装时经常遇到问题。最常见的是pip版本太老导致下载失败。先升级pippython -m pip install --upgrade pip如果下载速度慢可以换国内镜像源pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple注意镜像源地址可能会变化如果上面的地址失效可以搜索pip国内镜像源获取最新地址。安装完成后用下面这段代码验证import pygame print(pygame.ver) pygame.init() screen pygame.display.set_mode((400, 300)) pygame.display.set_caption(测试窗口) running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False pygame.quit()如果弹出一个空白窗口并且能正常关闭说明环境没问题。3.2 编辑器选择别用记事本折磨自己我见过有人用记事本写Python游戏然后抱怨缩进老是错。这不是编程问题是工具问题。VS Code免费插件丰富Python支持好推荐装Python和Pylance插件PyCharm Community免费版够用代码提示强适合大一点的项目Thonny专为初学者设计自带Python环境不用配置我个人习惯用VS Code轻量、启动快。但如果你完全没接触过编辑器Thonny可能更友好它会把变量值直接显示在界面上调试小游戏很方便。3.3 项目目录结构从小养成好习惯很多人写小游戏就是单个.py文件写到后面几百行改一个地方要找半天。建议从一开始就按这个结构组织my_games/ ├── games/ │ ├── snake.py │ ├── tetris.py │ └── ... ├── assets/ │ ├── images/ │ └── sounds/ ├── utils/ │ └── helpers.py └── main.pymain.py作为入口负责显示菜单和调度各个游戏。每个游戏独立一个文件互不干扰。assets放图片和音效utils放公共函数比如绘制文字、加载图片。这个结构看起来有点过度设计但等你写到第十个游戏的时候就会感谢自己当初这么做了。4. 核心游戏拆解贪吃蛇、打砖块、俄罗斯方块的底层逻辑4.1 贪吃蛇队列思想与碰撞检测的经典组合贪吃蛇看起来简单但它包含了游戏开发的两个核心概念状态更新和碰撞检测。蛇的身体本质上是一个队列。每次移动时在头部插入新坐标如果没吃到食物就删除尾部坐标。用Python的列表就能实现snake [(5, 5), (4, 5), (3, 5)] # 头在第一个 new_head (snake[0][0] dx, snake[0][1] dy) snake.insert(0, new_head) if not eat_food: snake.pop()这里有个容易忽略的细节方向键的响应。如果你直接用pygame.key.get_pressed()按住方向键会连续触发蛇会瞬间转向多次。正确的做法是用事件队列for event in pygame.event.get(): if event.type pygame.KEYDOWN: if event.key pygame.K_UP and direction ! DOWN: direction UP注意那个direction ! DOWN的判断——防止蛇直接掉头撞自己。这个细节不写游戏体验会很差。碰撞检测分两种撞墙和撞自己。撞墙就是判断头部坐标是否超出边界撞自己就是判断头部坐标是否在身体列表里除了头部本身。这两个判断写反了或者漏了游戏就会出现穿墙或者自杀不死的bug。实操心得贪吃蛇的移动速度不要设太快。我一开始设成每秒30帧移动一次结果根本反应不过来。后来改成每秒8-10次手感刚好。速度这个参数一定要做成可调的方便调试。4.2 打砖块速度向量与矩形碰撞的数学原理打砖块的核心是小球的运动。小球的位置由速度向量(vx, vy)决定每帧更新ball.x ball.vx ball.y ball.vy碰到左右墙壁时vx -vx碰到上下墙壁时vy -vy。这个逻辑很简单但矩形碰撞检测才是重点。pygame提供了Rect.colliderect()方法但直接用会有问题如果小球速度太快一帧移动了20像素而砖块只有10像素厚小球可能直接穿过砖块。这叫隧穿效应。解决方案有两个限制速度让小球每帧移动不超过砖块厚度的一半连续碰撞检测把移动拆成多个小步每步检测一次我一般用第一种简单有效。如果要做高速球就用第二种steps int(max(abs(ball.vx), abs(ball.vy)) / 5) 1 for i in range(steps): ball.x ball.vx / steps ball.y ball.vy / steps check_collision()反弹方向也有讲究。如果小球从下方撞到砖块应该vy -vy如果从侧面撞到应该vx -vx。判断依据是小球和砖块的中心点位置关系if abs(ball.centerx - brick.centerx) abs(ball.centery - brick.centery): ball.vx -ball.vx # 侧面碰撞 else: ball.vy -ball.vy # 上下碰撞这个判断逻辑我调了好几次才搞对一开始小球总是以奇怪的角度弹飞。4.3 俄罗斯方块矩阵旋转与行消除的算法实现俄罗斯方块是这几个游戏里算法最复杂的。核心难点有三个方块表示、旋转、行消除。方块表示我推荐用二维矩阵。比如T形方块T_SHAPE [ [0, 1, 0], [1, 1, 1], [0, 0, 0] ]旋转就是矩阵转置加翻转。顺时针旋转90度def rotate(matrix): return [list(row) for row in zip(*matrix[::-1])]这行代码很Pythonic但新手可能看不懂。拆解一下matrix[::-1]把行倒序zip(*...)把列变成行最后转成列表。效果就是顺时针旋转。行消除的逻辑是遍历每一行如果全是非零值就删除这一行并在顶部插入一个全零行。new_board [row for row in board if not all(row)] while len(new_board) len(board): new_board.insert(0, [0] * width)这里有个性能陷阱如果每帧都做全盘扫描方块多了会卡。优化方法是只检查方块落下的那几行而不是整个棋盘。注意俄罗斯方块的旋转还要处理踢墙问题。当方块贴着墙壁旋转时旋转后的位置可能超出边界需要尝试左右移动一格再旋转。这个细节不处理玩家会觉得很别扭。4.4 三个游戏的共通架构游戏循环的标准化写法写完这三个游戏你会发现它们的骨架是一样的import pygame pygame.init() screen pygame.display.set_mode((WIDTH, HEIGHT)) clock pygame.time.Clock() running True while running: # 1. 处理输入 for event in pygame.event.get(): if event.type pygame.QUIT: running False handle_event(event) # 2. 更新状态 update() # 3. 绘制画面 draw(screen) # 4. 控制帧率 clock.tick(60) pygame.quit()这个结构叫游戏循环是所有实时游戏的基础。把handle_event、update、draw拆成独立函数代码会清晰很多。我见过有人把所有逻辑塞在一个while循环里写到后面自己都看不懂。帧率控制也很重要。clock.tick(60)表示每秒最多60帧。不写这行游戏会跑满CPU风扇狂转。但也不要设太低低于30帧会明显卡顿。5. 从能跑到好玩那些教程不会告诉你的优化技巧5.1 手感调优速度、加速度与响应延迟游戏能不能让人玩下去手感比画面重要得多。手感的核心是三个参数速度、加速度、响应延迟。以贪吃蛇为例。移动速度太快玩家反应不过来太慢又觉得拖沓。我实测下来每秒8-12格是比较舒服的范围。但这不是固定的——随着蛇变长可以适当加速增加紧张感。打砖块的小球速度也有讲究。初始速度建议每秒200-300像素每打掉一块砖加速5%。这样前期轻松后期紧张节奏感就出来了。响应延迟指的是从按键到画面变化的时间。pygame默认是60帧也就是16毫秒一帧这个延迟人眼基本感觉不到。但如果你在update里做了大量计算帧率掉到30以下就会明显感觉按键不跟手。解决办法是把耗时的计算比如AI寻路放到单独的线程或者限制每帧的计算量。5.2 视觉反馈让玩家知道发生了什么新手做的游戏往往干巴巴的——球撞到砖块砖块消失了没有任何反馈。玩家会怀疑我到底打中了没有。加一点视觉反馈体验立刻不一样粒子效果砖块消失时炸出几个小方块屏幕震动碰撞时画面轻微抖动颜色变化蛇吃到食物时闪一下音效碰撞、得分、游戏结束都有对应音效粒子效果实现起来很简单就是生成一堆小矩形给它们随机的速度和生命周期每帧更新位置并减小透明度。class Particle: def __init__(self, x, y): self.x x self.y y self.vx random.uniform(-3, 3) self.vy random.uniform(-3, 3) self.life 30 def update(self): self.x self.vx self.y self.vy self.life - 1屏幕震动就是给整个画面加一个随机的偏移量持续几帧。这个效果在打砖块和俄罗斯方块里特别带感。5.3 难度曲线让游戏刚好有点难难度曲线设计是个玄学。太简单玩两分钟就腻太难玩两分钟就摔键盘。好的难度曲线应该是阶梯式上升每过一关难一点但不会突然变难。具体做法贪吃蛇每吃5个食物速度加10%同时增加一个障碍物打砖块每打掉一排砖球速加5%砖块血量加1俄罗斯方块每消除10行下落速度加一档关键是给玩家适应的时间。难度提升后至少让玩家玩30秒再提升下一次。突然的难度跳跃会让玩家觉得这游戏在针对我。5.4 存档与排行榜让游戏有再来一局的动力小游戏最容易流失玩家的地方是没有积累感。玩完一局分数清零下次打开还是从头开始。加一个简单的存档和排行榜留存率会高很多。存档用JSON就行import json def save_score(name, score): try: with open(scores.json, r) as f: scores json.load(f) except FileNotFoundError: scores [] scores.append({name: name, score: score}) scores.sort(keylambda x: x[score], reverseTrue) scores scores[:10] # 只保留前10 with open(scores.json, w) as f: json.dump(scores, f)排行榜显示的时候把前三名用不同颜色标出来玩家会更有动力冲榜。6. 踩坑实录我在写这30个游戏时遇到的那些破事6.1 中文显示乱码pygame字体渲染的坑pygame默认字体不支持中文直接渲染会显示方块。解决办法是加载系统中文字体font pygame.font.SysFont(simhei, 24) # 黑体 # 或者 font pygame.font.Font(C:/Windows/Fonts/simhei.ttf, 24)但SysFont在不同系统上名字不一样。Windows是simheiMac是pingfangLinux可能是wqy。跨平台的话最好把字体文件打包到assets里用Font加载。还有一个坑字体渲染很耗性能。如果每帧都重新渲染文字帧率会掉。正确做法是把渲染结果缓存起来只在文字变化时重新渲染。6.2 事件队列溢出为什么按键会失灵pygame的事件队列有大小限制。如果你在update里做了耗时操作事件处理跟不上队列满了之后新事件会被丢弃表现就是按键没反应。解决办法是每帧都清空事件队列即使你不处理某些事件for event in pygame.event.get(): if event.type pygame.QUIT: running False # 其他事件处理pygame.event.get()会返回并清空队列所以只要每帧调用一次就不会溢出。另一个常见问题是按键重复触发。按住方向键不放KEYDOWN事件会连续触发。如果你在KEYDOWN里直接改变方向蛇会瞬间转好几次。解决办法是用KEYDOWN设置一个待处理方向在update里再实际应用。6.3 帧率不稳定clock.tick的正确用法clock.tick(60)的意思是限制最高60帧不是固定60帧。如果你的代码跑得慢实际帧率会低于60。这时候游戏速度会变慢因为你的移动逻辑是按帧计算的。解决办法是用时间差计算移动而不是按帧dt clock.tick(60) / 1000.0 # 转换成秒 ball.x ball.vx * dt这样无论帧率多少小球每秒移动的距离是一样的。这个技巧叫帧率无关移动做游戏一定要掌握。6.4 内存泄漏那些忘记释放的资源pygame的Surface对象如果不释放会一直占内存。特别是粒子效果每帧生成几十个粒子几分钟就卡了。解决办法是及时删除死亡的对象particles [p for p in particles if p.life 0]音效对象也要注意。pygame.mixer.Sound加载后如果不使用可以用del删除。但更常见的问题是重复加载——每次播放都重新加载音效文件。正确做法是加载一次存到字典里复用。6.5 打包成exe让朋友也能玩你的游戏写完游戏想分享给朋友但朋友没装Python。这时候需要打包成exe。用pyinstallerpip install pyinstaller pyinstaller -F -w game.py-F表示打包成单个文件-w表示不显示控制台窗口。打包后的exe在dist目录下。但打包经常出问题资源文件找不到图片、音效的路径要用sys._MEIPASS处理文件太大pygame打包后至少30MB可以用--exclude-module排除不需要的模块杀毒软件误报这是pyinstaller的通病加个图标或者用--upx-dir压缩可以缓解实操心得打包前先在干净的环境里测试一遍确保没有遗漏的依赖。我遇到过打包后游戏能跑但没声音的情况查了半天发现是音效文件没被打包进去。7. 进阶改造把练手项目变成真正的作品7.1 从单机到联机socket通信的入门实践单机游戏玩久了会想要是能和朋友一起玩就好了。用Python的socket库可以实现简单的联机。核心思路是一个客户端做主机另一个做客机。主机负责游戏逻辑客机只负责发送输入和接收画面状态。# 主机 import socket server socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind((0.0.0.0, 9999)) server.listen(1) conn, addr server.accept() # 发送状态 conn.sendall(json.dumps(game_state).encode()) # 客机 client socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((主机IP, 9999)) data client.recv(1024) game_state json.loads(data.decode())联机游戏最难的不是通信而是同步。网络有延迟客机看到的画面总是慢半拍。简单的做法是客机只发送输入主机计算后广播状态。复杂一点可以用状态插值让画面平滑过渡。7.2 用PIL生成游戏GIF记录你的高光时刻想把自己玩游戏的精彩操作分享出来可以用PIL库把游戏画面录成GIF。from PIL import Image frames [] for i in range(100): # 每帧截图 pygame.image.save(screen, fframe_{i}.png) frames.append(Image.open(fframe_{i}.png)) frames[0].save(game.gif, save_allTrue, append_imagesframes[1:], duration50, loop0)duration50表示每帧50毫秒loop0表示无限循环。生成的GIF可以发到社交平台比截图有意思多了。7.3 代码重构从能跑到好维护写完30个游戏后回头看看第一个游戏的代码大概率会觉得这写的什么玩意。这是好事说明你进步了。重构的方向提取公共函数绘制文字、加载图片、碰撞检测这些每个游戏都要用的抽到utils里用类组织代码把游戏对象蛇、球、方块封装成类而不是用一堆全局变量加注释和文档字符串三个月后你还能看懂自己写的代码写单元测试核心逻辑比如俄罗斯方块的旋转写测试改代码时不怕改坏重构不是浪费时间而是把经验固化下来。你重构时做的每一个决定都是对之前学习的总结。7.4 发布与分享让作品被更多人看到游戏写好了别只放在自己电脑里。可以打包成exe发给朋友玩录屏发到视频平台把源码整理成教程分享参加一些编程社区的练手活动分享的过程会逼你把代码整理干净也会收到别人的反馈。这些反馈往往能指出你没想到的问题是进步最快的方式。8. 我的个人体会小游戏练手的真正价值写了这么多游戏我最大的体会是小游戏的价值不在游戏本身而在它逼你面对真实的问题。教程里的代码都是理想情况——没有边界情况没有性能问题没有用户乱按键盘。但游戏不一样。玩家会同时按三个键会在游戏结束时疯狂点击会把窗口拖到屏幕外面。这些意外逼着你去处理异常、优化性能、设计容错。还有一个体会是完成比完美重要。我见过太多人想做一个完美的游戏结果卡在某个细节上最后什么都没做出来。正确的做法是先做一个能跑的版本哪怕画面简陋、玩法单一然后再慢慢改。每改一次你就学到一个新东西。最后分享一个小技巧给每个游戏写一个README。不用很长就写清楚怎么运行、操作方式、已知问题。这个习惯看起来不起眼但等你回头找某个游戏的时候会感谢自己当初写了说明。这30个游戏我到现在还会偶尔打开玩两把。不是因为它们多好玩而是因为每一个都代表着我当时搞懂的一个知识点。看着它们跑起来就像看着自己的学习轨迹。这种感觉比任何教程都让人有动力继续学下去。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进