ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python pygame实战:从零开发2D小游戏完整教程

Python pygame实战:从零开发2D小游戏完整教程 这次我们来看一个特别适合 Python 初学者的项目用 pygame 从零写一款可玩的小游戏。不是把网上的完整源码拉下来跑一遍而是把场景、角色、掉落物、碰撞判定、计分规则、生命值、难度递增这些模块拆开重做全部用 Python 自带能力加 pygame 完成。整个项目不需要 GPU、不需要外部 API、不需要模型服务装好 Python 和 pygame 就能跑。如果你正在学 Python想做点能交互、有图形界面的东西从命令行脚本过渡到桌面程序这篇文章可以给你一条完整的练习路线。先给结论这个项目核心是一个 2D 简易小游戏玩家控制底部的挡板接金币、躲炸弹。技术栈是 Python 3 pygame 2.x代码量控制在 300 行以内素材直接用几何图形代替不需要准备美术资源。游戏支持得分、生命值、难度递增、重新开始、自动测试模式整体非常适合用来练习面向对象、事件循环和碰撞检测。文章会按“项目能力 - 适用边界 - 环境准备 - 代码设计 - 运行测试 - 性能观察 - 问题排查 - 最佳实践”的顺序展开。你可以直接把代码片段组合成一个可运行项目也可以只读思路再用自己的方式重写。1. 核心能力速览能力项说明项目类型Python 2D 小游戏主要玩法方向键控制挡板接金币、避开炸弹技术栈Python 3.8pygame 2.x核心功能精灵管理、碰撞检测、计分、生命值、难度递增、自动测试模式硬件要求普通 CPU 即可无 GPU 要求无显存占用支持系统Windows / macOS / Linux启动方式命令行运行python main.pyAPI 接口无网络 API提供命令行参数和 JSON 配置批量任务支持多组配置自动回归测试适合场景Python 入门练手、面向对象练习、小游戏原型开发这个项目最大的价值不是“做了一个游戏”而是用最小成本覆盖了 Python 编程里的关键概念类与继承、集合与碰撞检测、事件监听、时间循环、配置分离。游戏本身很简单但代码组织方式是工程化的后续想扩展成什么玩法都有清晰的切入点。2. 适用场景与使用边界这个项目最适合这几类人。第一类是刚学完 Python 基础语法、但不知道做什么练习的人。很多人的第一个 Python 程序只会在终端里跑print遇到图形界面就心虚。pygame 可以让你第一次看到“输入 - 逻辑 - 画面反馈 - 分数变化”的完整回路学起来比单独背知识点更有直观反馈。第二类是正在学面向对象编程的人。游戏里的玩家、金币、炸弹天然是“对象”每个对象有属性、方法和自己的绘制方式。通过给类加方法、调整属性能真正理解继承和封装的作用而不是死记语法。第三类是想快速验证小游戏原型的人。比如你在设计一个独立游戏关卡想先试一下掉落物密度、速度、碰撞范围不一定要用 Unity 这类重型引擎。Python pygame 足够快速验证核心玩法。使用边界也要说清楚。pygame 适合做 2D 原型和简单小游戏不适合做大型商业项目、3D 场景、复杂网络同步或者高性能物理模拟。如果需要大量美术素材也不建议硬用几何图形撑起整个产品。另一个重要边界是素材合规问题替换成真实图片、音效或背景音乐时必须使用自己制作的内容或者使用明确允许商用的素材不要从网上随手下载未授权资源。3. 环境准备与前置条件3.1 Python 安装检查先从 Python 开始。Windows 用户可以直接去 Python 官网下载安装包安装时需要勾选“Add Python to PATH”否则命令行里敲python会提示找不到命令。macOS 和 Linux 用户一般可以通过系统包管理器安装也可以使用官网安装包。安装完之后打开终端或命令提示符验证环境python --version pip --version如果pip提示不存在可以尝试python -m pip --version这条命令在 macOS 和 Linux 下更容易成功因为很多系统自带的 Python 没有把pip加入 PATH。3.2 安装 pygame确认 Python 可用之后直接安装 pygamepip install pygame建议安装 2.x 版本。pygame 2.x 在 macO 和 Windows 下的兼容性都比 1.9 好很多字体渲染、窗口缩放、帧率控制也更稳定。安装完成后运行一个测试命令python -m pygame.examples.aliens如果看到 pygame 自带的“外星人”示例窗口能正常打开、关闭说明环境已经可用。3.3 项目目录结构建议在本地建一个独立文件夹存放游戏代码我用的结构如下catch_game/ ├── main.py ├── settings.json └── test_batch.py不需要额外的虚拟环境但如果你同时在维护其他 Python 项目用虚拟环境更稳妥python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate这样可以避免不同项目之间的依赖冲突。3.4 系统依赖pygame 在 Windows 上基本是开箱即用。macOS 如果遇到音频相关报错通常是因为权限或驱动问题可以后面再处理Linux 可能需要安装一些系统库比如 SDL 相关依赖不过大多数发行版都能通过系统包管理器自动解决。这个游戏不使用外部图片和音频所以这部分依赖可以降到最低。4. 游戏设计与代码结构4.1 玩法拆解要做游戏先把玩法拆成可实现的模块玩家在屏幕底部左右移动用键盘方向键控制。掉落物从屏幕顶部随机生成并向下移动分为金币和炸弹两类。碰撞玩家接到金币加分碰到炸弹扣命。计分累计得分到达一定阈值后提高难度。游戏状态运行时、结束、退出。自动测试支持命令行参数控制方便后续做批量验证。这样做的好处是每个功能都能单独测试不会等到整段代码写完之后发现问题却无从下手。4.2 状态与帧率pygame 游戏的核心是主循环。主循环每秒刷新很多帧每一帧完成“读取输入 - 更新状态 - 绘制画面”这个顺序。游戏中的移动、掉落、碰撞都需要依赖一个稳定的帧率控制否则在不同电脑上运行速度不一样。配置文件中我会把窗口宽度、高度、帧率、玩家速度、金币分值、初始生命值、掉落物生成间隔、难度升级分数都集中管理后续调参数不用改代码。4.3 核心文件职责settings.json放所有数值参数main.py放游戏逻辑test_batch.py放批量自动测试逻辑。主文件内部按职责分成配置加载、精灵类、游戏主循环、命令行入口四个部分。这个分层思路可以复用到几乎所有 Python 桌面小项目上。5. 核心模块实现5.1 配置文件先写settings.json把所有可调参数集中起来{ screen_width: 800, screen_height: 600, fps: 60, player_speed: 8, coin_value: 10, start_lives: 3, spawn_interval_min: 20, difficulty_step_score: 100, coin_ratio: 0.8 }解释一下每个字段的含义screen_width和screen_height是游戏窗口大小fps是目标帧率player_speed是玩家移动速度coin_value是每个金币得分start_lives是初始生命值spawn_interval_min是最小生成间隔数字越小生成越快difficulty_step_score是每积累多少分提升一次难度coin_ratio是生成金币的概率0.8 表示八成概率生成金币、两成概率生成炸弹。把参数独立到配置文件之后调游戏难度就不需要改动游戏逻辑代码很大的后期维护成本会被省掉。5.2 配置加载与全局常量在main.py开头加载配置并设置颜色常量import json import random import sys import time import argparse import pygame def load_settings(pathsettings.json): with open(path, r, encodingutf-8) as f: return json.load(f) cfg load_settings() # 颜色定义 BG_COLOR (245, 245, 245) PLAYER_COLOR (70, 130, 180) GOLD_COLOR (255, 215, 0) BOMB_COLOR (200, 50, 50) BLACK (20, 20, 20) RED (200, 50, 50)颜色定义虽然不是配置项但集中写在文件顶部后面渲染分数、绘制角色时直接用代码会干净很多。5.3 玩家类玩家是游戏里唯一的可控对象只需要实现移动功能class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() self.width 80 self.height 20 self.image pygame.Surface((self.width, self.height)) self.image.fill(PLAYER_COLOR) self.rect self.image.get_rect() self.rect.midbottom (cfg[screen_width] // 2, cfg[screen_height] - 20) self.speed cfg[player_speed] def update(self, keys): left_pressed keys[pygame.K_LEFT] or keys[pygame.K_a] right_pressed keys[pygame.K_RIGHT] or keys[pygame.K_d] if left_pressed and self.rect.left 0: self.rect.x - self.speed if right_pressed and self.rect.right cfg[screen_width]: self.rect.x self.speed这里继承了pygame.sprite.Sprite所以玩家可以直接放进精灵组由精灵组统一负责更新和绘制。移动时加了边界判断防止角色滑出屏幕。除了方向键也支持 A 和 D 键照顾不同操作习惯。5.4 掉落物类接下来是金币和炸弹。两种对象结构很相似差异化在于颜色和碰撞后触发的效果class FallingItem(pygame.sprite.Sprite): def __init__(self, kind): super().__init__() self.kind kind size 24 self.image pygame.Surface((size, size), pygame.SRCALPHA) center size // 2 if kind coin: pygame.draw.circle(self.image, GOLD_COLOR, (center, center), center) else: pygame.draw.circle(self.image, BOMB_COLOR, (center, center), center) self.rect self.image.get_rect() self.rect.x random.randint(0, cfg[screen_width] - size) self.rect.y -size self.speed random.randint(3, 6) def update(self): self.rect.y self.speed if self.rect.top cfg[screen_height]: self.kill()掉落物在创建时随机出现在窗口顶部的水平位置然后每帧向下移动。移出屏幕底部时调用kill()把自身从精灵组里移除避免对象无限累积。这里用random.randint控制速度让掉落节奏产生变化比所有物体匀速下落更有可玩性。5.5 主循环与碰撞检测游戏主体是run_game函数。每一帧按照“事件 - 玩家移动 - 生成掉落物 - 更新掉落物 - 碰撞判定 - 绘制画面”的顺序执行def run_game(max_seconds0): pygame.init() screen pygame.display.set_mode((cfg[screen_width], cfg[screen_height])) pygame.display.set_caption(CatchGame - 接金币小游戏躲开炸弹) clock pygame.time.Clock() start_time time.time() player Player() all_sprites pygame.sprite.Group(player) items pygame.sprite.Group() score 0 lives cfg[start_lives] difficulty 1 spawn_timer 0 running True frame_count 0 while running: clock.tick(cfg[fps]) frame_count 1 for event in pygame.event.get(): if event.type pygame.QUIT: running False if event.type pygame.KEYDOWN and event.key pygame.K_ESCAPE: running False keys pygame.key.get_pressed() player.update(keys) if max_seconds 0 and time.time() - start_time max_seconds: running False if spawn_timer 0: kind coin if random.random() cfg[coin_ratio] else bomb item FallingItem(kind) items.add(item) all_sprites.add(item) spawn_timer max(cfg[spawn_interval_min], 40 - difficulty * 2) else: spawn_timer - 1 items.update() for hit in pygame.sprite.spritecollide(player, items, True): if hit.kind coin: score cfg[coin_value] else: lives - 1 if lives 0: running False if score // cfg[difficulty_step_score] difficulty: difficulty 1 screen.fill(BG_COLOR) all_sprites.draw(screen) font pygame.font.SysFont(simhei, 24) score_surface font.render(f得分: {score}, True, BLACK) lives_surface font.render(f生命: {lives}, True, RED) screen.blit(score_surface, (16, 16)) screen.blit(lives_surface, (cfg[screen_width] - 140, 16)) pygame.display.flip() elapsed time.time() - start_time avg_fps frame_count / elapsed if elapsed 0 else 0 pygame.quit() return score, difficulty, avg_fps这个主循环已经把游戏完整串起来了。spritecollide是 pygame 提供的碰撞检测方法它检查玩家和掉落物精灵组的碰撞命中后自动从组里移除碰撞到的对象返回值就是撞到的精灵列表。这里对每个碰撞对象判断类型金币加分炸弹减命。难度提升逻辑也简单累计得分除以难度阈值就能知道已经升到了第几级。5.6 命令行入口与自动测试模式为了让游戏支持自动化验证我加了--auto-test参数。传入秒数后游戏会自动运行指定时间然后退出不用手动操作def main(): parser argparse.ArgumentParser(descriptionCatchGame 自动测试模式) parser.add_argument(--auto-test, typeint, default0, help自动运行秒数0 表示手动模式) args parser.parse_args() score, difficulty, avg_fps run_game(max_secondsargs.auto_test) print(fscore{score}, difficulty{difficulty}, avg_fps{avg_fps:.1f}) if __name__ __main__: main()手动运行时直接执行python main.py自动验证时执行python main.py --auto-test 1010 秒后程序退出并打印得分、难度和平均帧率。这个机制对后续批量测试很有用改配置、跑脚本、看结果全程不需要人工操作。6. 启动运行与功能测试6.1 首次启动确保当前目录有main.py和settings.json然后运行python main.py启动后应该出现一个 800x600 的窗口背景为浅色底部有一个蓝色挡板顶部会不断掉落金色圆形金币和红色炸弹。按左右方向键或 A/D 键控制挡板移动。接到金币左上角分数增加 10 分碰到炸弹右上角生命值减少 1生命归零时窗口自动关闭。6.2 功能测试清单按下面的表格做一轮快速验证测试项操作预期结果判断标准窗口启动运行python main.py窗口正常打开且不掉帧窗口出现标题正确玩家移动按住方向键挡板左右移动且不超出边界移动顺畅有边界限制金币碰撞控制挡板接触金币左上角得分增加每次增加coin_value炸弹碰撞控制挡板接触炸弹右上角生命减少每次减少 1 点生命游戏结束让生命降到 0窗口关闭程序退出退出无报错自动测试运行python main.py --auto-test 55 秒后自动退出并打印数据打印 score、difficulty、avg_fps6.3 自动测试模式验证执行下面的命令python main.py --auto-test 5正常输出格式类似score60, difficulty1, avg_fps60.0如果输出正常说明游戏逻辑和主循环都能稳定跑通。这个模式也可以用来验证不同配置下游戏是否会出现异常比如把coin_ratio改成 0持续生成炸弹运行时是否卡死、是否能正常自动退出。6.4 批量任务示例项目本身是桌面游戏没有网络 API。需要做“批量任务”时可以通过test_batch.py使用多组配置反复跑自动测试import json import subprocess configs [ {player_speed: 6, spawn_interval_min: 25}, {player_speed: 8, spawn_interval_min: 20}, {player_speed: 10, spawn_interval_min: 15}, ] results [] for cfg_item in configs: backup json.load(open(settings.json, r, encodingutf-8)) backup.update(cfg_item) with open(settings.json, w, encodingutf-8) as f: json.dump(backup, f, ensure_asciiFalse, indent2) ret subprocess.run( [python, main.py, --auto-test, 5], capture_outputTrue, textTrue ) results.append({ config: cfg_item, returncode: ret.returncode, stdout: ret.stdout.strip() }) print(results)批量测试时要注意脚本会直接改写settings.json所以最好先备份一份默认配置测试完再恢复。如果一组配置下游戏运行异常returncode会非 0脚本能快速暴露问题。这虽然不是传统意义上的“批量任务”但对调难度、调刷新率这类重复工作非常有用。7. 资源占用与性能观察这个游戏不需要 GPU也没有模型推理任务资源占用的重点在 CPU、内存和帧率稳定性。运行时可以在系统的任务管理器或资源监视器里观察进程。通常 pygame 小游戏的内存占用不会高到哪去但如果你批量生成大量掉落物且没有及时清理对象会一直堆积表现为内存缓慢上升。代码里已经对移出屏幕的掉落物调用kill()正常情况下不会出现持续泄漏。帧率是一个重要指标。代码用clock.tick(cfg[fps])把目标帧率锁在 60超过这个数值的多余计算会被延后这样可以避免在 144Hz 高刷屏上跑得过于快导致游戏速度失控。自动测试模式里返回了avg_fps如果在自己电脑上明显低于 60说明性能存在问题可以优先检查是否开了太多后台程序、窗口大小是否过大或者老电脑上 CPU 占用过高。如果想进一步降低资源占用有几个方向。一是减小窗口尺寸从 800x600 降到 640x480二是减少掉落物生成频率三是在绘制图片时使用小尺寸图形避免不必要的缩放。实际数字以本机运行结果为准不用追求极端优化。8. 常见问题与排查方法问题现象可能原因排查方式解决方案pip 安装 pygame 失败网络问题或 pip 版本过旧查看报错信息尝试国内镜像pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simpleimport pygame报错pygame 未安装到当前 Python 环境运行python -m pip list重新安装 pygame或激活正确的虚拟环境窗口一闪而过主循环结束后立即退出检查while running是否执行按本文结构写主循环避免pygame.quit()在意外位置被调用方向键无响应游戏窗口没有获得键盘焦点点击游戏窗口后再按键确认窗口不是后台状态中文文字显示方块系统缺少中文字体打印pygame.font.get_fonts()换用系统可用字体名或用英文字体替代游戏速度过快未限制帧率检查主循环是否有clock.tick加上clock.tick(cfg[fps])自动测试无输出--auto-test参数被拼写错误运行python main.py --help确认参数名正确修改配置后不生效配置文件路径不对或格式错误用python -c import json; json.load(open(settings.json))检查修复 JSON 格式保证配置文件与main.py同目录这里重点说一个常见问题如果你把settings.json里的screen_width改成比窗口还小的值掉落物的生成范围也会自动变小因为生成随机数使用的是配置里的宽度值。这种改动虽然不会报错但玩法会明显变化。调整参数时最好一次只改一个字段跑一次自动测试确认效果。9. 最佳实践与扩展方向9.1 工程化建议第一配置和代码分离。所有数值参数都放settings.json不写死在代码里后续调难度、调玩法都很方便。第二分类管理文件。建议把音效资源、图片资源、字体文件都放在独立目录下比如assets/images/、assets/sounds/、assets/fonts/不要和脚本文件混在一起。第三第一次调整邻域先做小参数测试。比如把coin_ratio改成 0.5先用自动测试跑 10 秒观察游戏节奏再决定是否保留新配置。9.2 可以继续扩展的方向这个基础版还有很多可以玩的空间添加开始界面和结束界面用键盘或鼠标操作重新开始。加入不同分值的道具比如金色大金币、加分星星、减速道具。给掉落物增加左右漂移效果让移动轨迹更有变化。加入本地最高分记录把分数保存到 JSON 文件。使用pyinstaller打包成独立可执行文件方便分发给朋友测试。打包时需要注意pygame 应用需要把依赖一起打入建议先安装pyinstaller并用pyinstaller --onefile main.py打包。第一次打包时注意生成的dist目录下是否有独立可执行文件运行时是否能找到同目录的settings.json和相关资源。9.3 合规与分享提醒替换游戏素材时不要直接使用来源不明的图片和音频。图标、背景、音效尽量自己绘制或录制也可以使用明确标注“可商用”的素材库。如果未来在公开平台发布游戏或者参加比赛特别注意字体授权、音效授权和素材版权。10. 总结与下一步这个项目最值得尝试的地方是它能用很短时间让你看到 Python 程序的完整可视化结果。你不再只是在终端里看到文字而是屏幕上有一个会移动、会碰撞、会计分的游戏世界。代码拆得足够细每加一个新功能都有清晰的落点适合反复重构。先验证的功能是主循环是否能稳定跑起来。只要窗口打开、挡板能移动、金币和炸弹能正常掉落整个项目的核心就已经跑通了。最容易踩的坑有三个配置文件写错导致游戏启动异常、中文显示乱码、不同电脑上帧率不一致导致游戏速度不同。前两个靠检查配置和字体解决第三个靠clock.tick限制帧率解决。后续扩展建议从“开始界面”和“最高分记录”入手这两个功能与现有代码耦合度低能迅速看到成果。如果想往自动化方向走可以完善test_batch.py把更多配置组合加入批量测试让每次改动都有数据反馈。建议把这篇流程收藏等你把基础版跑通之后再回头对照扩展方向做升级。
返回列表