简介基于Pygame打造的赛车小游戏《逆行飙车》完整源码适合Python初学者与游戏开发爱好者作为实战练习项目。项目实现了键盘方向键控制车辆移动、碰撞检测、障碍物生成等核心玩法并配有背景音乐与碰撞音效能帮助读者快速理解Pygame事件处理、游戏主循环、图形渲染及简单物理判断等关键知识点。压缩包共12个文件体积仅1.95MB包含1个主程序py文件、4个字体文件、4张游戏素材图片、2段mp3音频以及1份README说明文档结构清晰素材齐全下载解压后即可运行体验。目前已有2025人学习下载对于想通过具体项目提升Python编程能力、入门2D游戏开发的读者来说是一份轻量且完整的参考源码。1. python 赛车小游戏源代码不是玩具逆行飙车在做什么第一次拿到 python 制作的赛车小游戏源代码很多人都会点开 main.py 翻几页然后说一句这也太简单了一辆小车、三条车道、左右躲车十分钟就能玩腻。但如果标题里带了“逆行飙车”四个字玩法重心就完全变了它要求你主动开进对向车道用迎面车流去换更高得分。收益越大风险越大玩家的紧张感就从这里来。这个标题背后其实是一份典型的中级 Python 图形项目主要解决三件事用 Pygame 搭一个能跑的游戏主循环用坐标和碰撞检测堆出竞速感再用一套计分规则把“危险驾驶”翻译成正向反馈。适合刚过 Python 入门期、想从脚本走向图形界面的人也适合下载过各类 python 赛车源码却总跑不起来、想搞懂每行代码为什么这么写的人。和 C 写游戏不同Python 版不必跟内存和指针搏斗而要把精力放在逻辑顺序上这是新手第一次自己能改出“手感”的项目。2. 选 Pygame 而不是 Tkinter项目骨架、主循环与逆行坐标系2.1 为什么是 Pygame三个库摆在面前时的取舍常见做法是用 Pygame但这并不只是因为大家都在用。先看另外两个选择Tkinter 是 Python 自带的 GUI 库能做按钮、文本也能做动画但它的 Canvas 重绘效率低一旦每帧要更新几十个运动物体就会出现肉眼可见的闪烁pygame zero 把主循环隐藏得非常干净几行代码就能画图适合教学演示但它把事件、碰撞、坐标全部“代管”了你很难看清方向盘到底在谁手里。Pygame 的好处是“半裸奔”窗口、事件、图像、碰撞靠自己调主循环自己写帧率自己锁。这正好贴合“源代码”这个诉求——你拿到源码后不是为了收藏是要改的。改 Pygame 代码时你能清楚看到每一次坐标更新发生在哪一行而不是黑匣子。安装方式也简单一行命令的事pip install pygame这条命令会把 pygame 装进当前 Python 环境。如果你机器上同时存在多个 Python 版本建议先确认python --version指向的是你要用的那个否则会出现“明明装好了运行时仍说找不到模块”的问题这也是后面避坑章要展开的典型翻车现场。参数说明这里不指定版本号直接装最新稳定版即可如果你在 macOS 上遇到权限提示常见做法是加参数pip install --user pygame装在用户目录下避开系统目录写入权限。Windows 上如果 pip 不在 PATH 里可以用python -m pip install pygame代替这套写法能保证安装目标和执行目标一致。2.2 骨架先立住单文件跑通和分模块的边界拿到一份 python 赛车小游戏源代码第一件事不是从头到尾读而是看文件结构。常见结构有两种一种是“单文件跑通”所有类、函数、主循环挤在一个 main.py 里适合最小验证另一种是“工程化骨架”把参数、玩家、敌车、绘制拆开。我一般建议先走单文件等调到能玩再拆。文件职责什么时候需要拆main.py主循环、事件、碰撞、绘制最小可玩版只此一个settings.py屏幕尺寸、车道数、速度、生成间隔需要反复调参数时car.py玩家类、敌车类含移动和碰撞矩形加车辆类型、AI 变道时road.py路肩线、车道线、逆行道标记的绘制想做多块地图时分模块不是越早越好。对新手来说所有逻辑放在一个文件里你改一处立刻能看到结果心智负担最小当你能提出“我想把难度曲线做成 JSON 配置”这类需求时再拆 settings.py 不迟。单文件不会让你写错只会让你后期找代码时多滚两下鼠标这不叫技术债叫成长税。2.3 坐标系先于代码y 轴向下的世界怎么读Pygame 的坐标系跟数学课上的不一样原点在窗口左上角x 轴向右y 轴向下。也就是说车的“前进”方向并不一定是坐标系正方向。逆行飙车这个玩法里玩家车固定在下半屏对向车从屏幕顶部生成、向下移动碰撞就发生在两者之间。新手最常见的误解是拿数学坐标系套想让车往上走就 y 增加结果车直接飞出屏幕顶。在这个项目里坐标系只需要记住三条约定第一条车道从左到右编号 0、1、20 号道是逆行道第二条所有车辆用矩形Rect表示碰撞就用矩形相交判断第三条玩家按上键表示“加大油门”它不影响玩家车纵向坐标而是让所有对向车移动速度变快。这样做的好处是不用处理玩家车频繁上下移动导致的相机跟随问题代码量少一半但驾驶感和危险感一点不少。到了后面的代码实现章节你会看到这三个约定全部落进了几十行代码里。3. 让地图活起来路肩滚动、对向车流与逆行计分的核心机制3.1 用取模让路肩线滚起来而不是挪一组线段赛车游戏里最骗人的一个细节是车没动路在动。逆行飙车的地图是一个纵向滚动的道路玩家车固定在屏幕下部对向车向下冲这时候路面必须同步滚否则你看到的就是“车在冰面上滑行路面纹丝不动”。最直观的做法是维护一个线段列表每帧给每条线段的 y 坐标加上速度线移出屏幕底部就删掉再在顶部补一条。这个做法逻辑没错但问题在于列表长度会随着帧数波动删和增的位置一旦算错就会出现整条虚线“跳变”的情况。我一般用取模来实现滚动核心就一句话dash_off (frame * 6) % 40每一帧用一个全局帧号 frame 乘以速度系数再对线段间距 40 取模得到的 dash_off 就是本帧虚线应该偏移的像素数。绘制时所有虚线的 y 坐标都加上 dash_off从 -40 开始、每隔 40 像素画一条循环到屏幕底部以下。这段代码的逻辑是因为所有虚线都共用同一个偏移量所以它们之间的相对位置永远不变看上去就像一整条长虚线在匀速向下滚。取模保证了偏移量永远落在 0 到 39 之间不会累加到溢出。关键参数是6和406 是每帧滚动像素数直接对应视觉速度调大路就滚得快40 是虚线周期实际绘制时每条虚线是 20 像素宽间隔也是 20 像素这样虚实比是 1:1高速滚动时不会闪。如果你要做出“路面越来越快”的感觉就把这个系数跟玩家油门状态绑定而不是全局写死。3.2 对向车流密度与车道权重逆行道的“危险溢价”对向车流是整个游戏的风险来源。如果把每辆车生成在同一概率下三条车道的危险度完全一样那你辛辛苦苦设计“逆行”玩法就毫无意义。玩家只有在逆行道明显更危险、得分又明显更高时才会在“冒险”和“保守”之间做决策。常见做法是给车道分配不同的生成权重。在 Python 里用random.choices是最直接的lane random.choices([0, 1, 2], weights[0.5, 0.3, 0.2])[0]weights 列表里的三个数字对应三条车道被选中的概率。0 号道逆行道权重最高会刷出最多的对向车2 号道最右侧车最少是新手的安全通道。这样设计的目的是让玩家不由自主地想往右靠但右道得分又低玩家必须时刻在“去左边冒险”和“留在右边安稳”之间来回摇摆。车流的另一个关键参数是生成间隔。每一帧都判断一次是否生成新车每spawn_gap帧生成一辆。间隔越小车流越密。如果把间隔写死成固定值游戏前 30 秒和 3 分钟后的体验完全一样玩家很快会腻。我一般会做一个简单的时间压力spawn_gap max(10, 26 - frame // 500)frame 是全局帧号每 500 帧约 8 秒就把生成间隔缩短一点最多缩到 10 帧一辆。max的作用是防止难度无限上涨间隔低于 10 帧后不再继续缩否则车流会密到玩家完全没有反应时间那不是游戏那是幻灯片。这个参数你要按自己的反应速度调30 岁和 15 岁的玩家差距极大做成配置项是最值的。还有一个细节容易被忽略同一帧生成的多辆敌车如果落在同一车道玩家会看到“车叠罗汉”。因为新生成的敌车 y 坐标都在屏幕顶部附近两车几乎重叠。一个简单的兜底方案是生成后检查与已有车辆的距离太近就重新选车道。在这个最小版里我用比较粗暴的方式绕过了每帧最多生成一辆且生成间隔不小于 10 帧这样两辆车在垂直方向上有天然间距基本不会叠。3.3 逆行计分的值域设计油门与车道的激励叠加计分规则决定玩家的行为取向。逆行飙车这个标题隐含的需求是玩家要“飙车”而且要“逆行”所以计分必须同时奖励油门和逆行并且两者要产生叠加效应。我设计的计分规则是这样的gain 1 if player.throttle: gain 2 if player.in_reverse: gain * 2 score gain每分钟基准得分是 60 分每帧 1 分60 帧每秒。踩油门时每帧加 3 分是基准的三倍进入逆行道后整个得分再乘 2最高可以达到每帧 6 分一分钟 360 分。这个乘法的设计意图很明显不踩油门、走安全道稳定但低收益踩油门 逆行收益翻倍但要冲进最危险的车道。得分的数值感也要控制。如果每帧只加 1 分玩家会觉得数字在爬行如果每帧加 10 分死亡结算时数字失去冲击力。1、3、6这个阶梯是我试出来的舒服区间安全道 1 分油门到 3逆行加踩油门到 6数字变化足够敏感但不会快到失去意义。做游戏数值时记住一个原则分数要能读出“我刚才做了哪种选择”而不只是一个越滚越大的数字。4. 最小可玩版源码100 行跑起逆行飙车的完整代码与调参4.1 一份直接能跑的 main.py下面的代码是完整体不依赖任何图片、音效素材直接复制到本地命名为 main.py在 Python 环境里执行python main.py就能玩。所有画面元素都用 Pygame 的绘图函数画出来所以即便你没有美术素材也不会看到黑屏。代码用到了前面章节讲到的坐标系约定、取模滚动、车道权重和计分激励。import pygame import random pygame.init() W, H 480, 640 GAME_W 330 LANE_N 3 LANE_W GAME_W // LANE_N MARGIN (W - GAME_W) // 2 FPS 60 TRAFFIC_SPEED 5 PLAYER_SPEED 5 THROTTLE_EXTRA 2 LANE_WEIGHTS [0.5, 0.3, 0.2] class Player: def __init__(self): self.w, self.h 44, 72 self.x MARGIN LANE_W (LANE_W - self.w) // 2 self.y H - 180 self.throttle False self.in_reverse False self.alive True def update(self, keys): if keys[pygame.K_LEFT]: self.x - PLAYER_SPEED if keys[pygame.K_RIGHT]: self.x PLAYER_SPEED self.x max(MARGIN 4, min(MARGIN GAME_W - self.w - 4, self.x)) self.throttle keys[pygame.K_UP] self.in_reverse self.x MARGIN LANE_W property def rect(self): return pygame.Rect(self.x 8, self.y 10, self.w - 16, self.h - 20) class Enemy: def __init__(self): self.w, self.h 44, 72 lane random.choices([0, 1, 2], weightsLANE_WEIGHTS)[0] self.x MARGIN lane * LANE_W (LANE_W - self.w) // 2 self.y -self.h self.speed TRAFFIC_SPEED random.random() * 2 def update(self, extra): self.y self.speed extra property def rect(self): return pygame.Rect(self.x 8, self.y 10, self.w - 16, self.h - 20) def main(): screen pygame.display.set_mode((W, H)) pygame.display.set_caption(逆行飙车 - 最小可玩版) clock pygame.time.Clock() player Player() enemies [] score 0 frame 0 while player.alive: for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() return keys pygame.key.get_pressed() player.update(keys) spawn_gap max(10, 26 - frame // 500) if frame % spawn_gap 0: enemies.append(Enemy()) extra THROTTLE_EXTRA if player.throttle else 0 for en in enemies[:]: en.update(extra) if en.y H: enemies.remove(en) elif player.rect.colliderect(en.rect): player.alive False gain 1 if player.throttle: gain 2 if player.in_reverse: gain * 2 score gain screen.fill((40, 44, 52)) pygame.draw.rect(screen, (70, 70, 78), (MARGIN, 0, GAME_W, H)) dash_off (frame * 6) % 40 for lane in range(1, LANE_N): line_x MARGIN lane * LANE_W for y in range(-40, H 20, 40): pygame.draw.rect(screen, (255, 255, 255), (line_x - 3, y dash_off, 6, 20)) pygame.draw.rect(screen, (220, 180, 60), (MARGIN - 6, 0, 6, H)) for en in enemies: pygame.draw.rect(screen, (200, 60, 60), en.rect) pygame.draw.rect(screen, (70, 130, 240), player.rect) font pygame.font.SysFont(arial, 24) score_surf font.render(SCORE: {}.format(score), True, (255, 255, 255)) screen.blit(score_surf, (12, 10)) pygame.display.flip() frame 1 clock.tick(FPS) font_big pygame.font.SysFont(arial, 36) over_surf font_big.render(CRASHED! SCORE: {}.format(score), True, (240, 60, 60)) screen.blit(over_surf, (W // 2 - over_surf.get_width() // 2, H // 2 - 20)) pygame.display.flip() pygame.time.wait(2000) if __name__ __main__: main()4.2 循环里三个关键逻辑滚动、碰撞、计分这段代码的核心逻辑都在 while 循环里我拆开讲一遍你以后改任何 pygame 赛车小游戏源代码时都能复用这套思路。第一个是地图滚动。dash_off (frame * 6) % 40这行决定了路面视觉滚动速度前面的章节已经提过原理。这里要补充的是绘制顺序先铺灰色路面底色再画虚线再画最左侧的逆行道黄线最后画车辆。绘制顺序决定了遮挡关系虚线一定在路面之上、车辆之下否则车会压住车道线视觉上就像车飞在路面外。很多初学者把绘制顺序写反结果车像在玻璃上滑行。第二个是碰撞判定。玩家和敌车的rect都是缩了一圈的矩形横向各缩进 8 像素纵向顶部缩进 10 像素、底部缩进 10 像素。这样做的原因很简单车身是个圆角矩形四个角是透明的如果用矩形外框判断碰撞两车“擦肩而过”时明明没接触也算撞上玩家会觉得游戏判定太严。缩圈后的 rect 让碰撞边界更贴近真实车身轮廓。player.rect.colliderect(en.rect)是 Pygame 提供的矩形相交判断一次调用同时比较了 x、y、宽、高四个维度比自己写条件判断更省心。第三个是计分叠加。计分逻辑放在碰撞判断之后所以玩家车死亡的那一帧不会额外加一分这是为了防止玩家卡着最后一帧刷分。顺序上先刷新车、再判碰撞、再计分、最后绘制这个顺序不要乱如果你先计分再判碰撞死亡瞬间会多加一帧分数结算数字会不稳定玩家会感觉分数“跳了一下”。4.3 必调参数表先用默认再按手感微调代码顶部集中了所有可调参数这就是最简单的配置文件。下面是每个参数的默认值和调整方向。参数默认值作用调整手感TRAFFIC_SPEED5对向车基础下落速度调到 7 游戏明显变难调到 3 适合新手PLAYER_SPEED5玩家左右横移速度手感“肉”就调大调到 8 会有漂移感THROTTLE_EXTRA2踩油门时敌车额外加速度调大后油门惩罚更明显LANE_WEIGHTS[0.5, 0.3, 0.2]三条车道的刷车概率左道调 0.6 以上会变得几乎不可行spawn_gap 初始值26车流生成间隔调小到 20 以下开局压力就上来了调参的原则是一次只改一个。比如你把 TRAFFIC_SPEED 从 5 调到 7觉得太难不要立刻又把 PLAYER_SPEED 调大因为两个参数叠加后你很难判断到底是速度变难了还是横向移动太灵敏。先把一个参数往一个方向推到极限记录体验再拉回来。这个习惯比任何优化技巧都重要因为游戏手感本质上就是参数曲线不是玄学。5. 逆行飙车源码的五个高频翻车点现象、原因与排查5.1 报错 ModuleNotFoundErrorNo module named pygame现象从网上拿到 python 赛车小游戏源代码双击运行或python main.py控制台直接抛红字提示找不到 pygame 模块。原因当前 Python 环境里没装 pygame或者你装到了另一个 Python 版本上。最常见的是电脑里同时装了 Python 3.8 和 3.11命令行执行python指向的是 3.8而 pip 安装被默认指向了 3.11。这不是代码问题是环境问题。解决统一用python -m pip install pygame来安装安装完再用python -c import pygame; print(pygame.__version__)验证。如果仍报错检查命令行里的 python 路径是不是 python.org 官网下载的那个。网上 python 安装教程五花八门有的教程会改 PATH有的会多装一个 Windows 应用商店版最容易出问题的反而是这种“看起来装好了”的环境。5.2 车从“图片”里穿模撞到了却没反应现象敌车和玩家车明明在画面上重叠了但游戏就是不判定死亡或者反过来两车差一个车身宽就撞上了判定却提前触发。原因碰撞用的矩形太大或者太小。如果你直接拿素材图片的原始宽高做 Rect而图片四周有大量透明留白那矩形最大边界就包含了全部透明区域。玩家看着是“擦肩而过”程序里其实是“撞上了”这类冲突最容易让人骂游戏判定垃圾。解决缩圈。看代码里的rect属性横向缩 8 像素、纵向各缩 10 像素这个数值是根据车身视觉轮廓试出来的。不同素材的留白比例不一样拿到新素材第一件事就是打印图片尺寸再对照视觉轮廓调缩圈值。我一般会在调参阶段给车辆画一个半透明的碰撞框pygame.draw.rect(screen, (0, 255, 0), en.rect, 1)加上这行调试代码碰撞框就能显示出来。如果绿框明显比车身大缩圈值继续加大如果绿框已经缩进了车身内部说明缩多了。调试完记得删掉这行或者用一个DEBUG开关包起来。5.3 帧率忽快忽慢手感像开了变速齿轮现象同样的代码AI 车下落速度在某一秒突然变快过几秒又恢复正常同一台电脑上跑两次第一次比第二次流畅。原因游戏循环没有锁帧。Pygame 的循环速度取决于 CPU 忙不忙后台有下载任务时循环就慢空闲时循环就快。如果速度参数是固定的“每帧移动 5 像素”帧率高时车速就快帧率低时车速就慢手感完全失控。这是赛车游戏里最容易翻车的一类问题。解决加clock.tick(FPS)代码里已经用上了。它的作用是把循环强制控制在每秒 FPS 次默认FPS 60。注意它不是万能的如果机器性能不够实际帧率会低于 60游戏只会变慢不会崩。真正严格的方案是用 delta time也就是上一帧到这一帧的真实时间差乘以速度让移动距离和时间挂钩但最小可玩版里clock.tick(60)已经足够了。5.4 对向车全挤在一条道逆行关变成赌命关现象理论上三条车道按权重生成但实际跑起来经常出现连续 5 辆车都刷在 0 号道玩家一进逆行道就被连环追尾。原因random.choices的权重只保证长期统计上接近 0.5、0.3、0.2但在短时间窗口内随机数神出鬼没连续命中同一个车道很正常。这是随机算法的本性不是 bug但玩家体验上就是 bug。解决在生成函数里加一个“最近车道记忆”。常见做法是记录上一次生成的车道下一次生成时如果随机结果和上次相同强制换到相邻车道。这个补丁不能完全消除拥堵但能把连续同道的概率压到可接受范围。另一种做法是生成后检查与现有车辆的距离距离小于一个车身就重新选道。对于最小版代码最简单有效的兜底是把spawn_gap下限调到 15 以上用时间间隔稀释连续风险。5.5 换了台电脑同样的代码速度完全不同现象在开发机上玩着正合适打包或拷到另一台电脑车流密度和车速都变了难易度完全不是同一个游戏。原因一个是帧率没锁死另一个是屏幕分辨率变了。如果你把窗口宽高写死在代码里那 PPI 高的屏幕上窗口还是 480x640但显示密度不同观感会奇怪而帧率方面如果开发机是 120Hz 高刷屏而你没锁帧实际速度会翻倍。前者是观感问题后者直接是手感灾难。解决锁帧是必须的代码里的clock.tick(FPS)已经做了。分辨率方面如果要适配不同窗口不要改坐标硬编码而是按窗口宽度重新计算MARGIN和LANE_WW pygame.display.Info().current_w然后让GAME_W按比例缩放。这行代码用了 Pygame 查询当前显示模式的功能可以在不同分辨率屏幕上自动缩放道路宽度。注意缩放后车辆宽度和速度并不会自动等比变化你需要在代码里加一个scale系数把 PLAYER_SPEED 和车宽都乘上它否则高分辨率下赛道宽了车却还是原来大小视觉比例会失真。这个问题没有万能解只能靠多设备实测别指望一套参数走天下。6. 最后一公里把“能跑”验证成“能玩”6.1 三分钟验证清单代码跑起来不算完能跑和能玩之间隔着一条鸿沟。我的习惯是做完一个小游戏后先用三分钟清单过一遍合格了才叫可玩。第一步关掉所有编译器和调试输出用python main.py直接跑确认没有控制台报错。第二步把 FPS 显示在屏幕上心里默数一秒看帧数是不是稳定在 55 到 60 之间。如果掉帧先看是不是敌车列表无限膨胀——你可以在循环里打印len(enemies)超过 20 就要检查清理逻辑。第三步故意开车去撞对向车确认碰撞位置和视觉一致是车头撞车头还是隔空判定。第四步连续玩两分钟感受生成间隔变化曲线是否平滑。如果从第 30 秒开始车流突然变密那是因为frame // 500这个公式太线性这时候改成26 - int(frame // 500)**1.1难度增长会变得更柔和。第五步叫旁边的人玩一局看他在哪里死掉。如果他连续三次都死在同一个位置说明那个位置的难度峰值太高不是玩家菜。6.2 下一个 10 分钟把配置和难度曲线拆出去最小版能让游戏跑起来但它所有的参数都硬编码在文件顶部改一次要开一次编辑器。正确方向是把这些参数抽成一个 JSON 文件启动时加载。做法很简单把窗口尺寸、车道数、基础速度、车道权重、难度增长系数写进配置代码里用json.load()读入。这个改动会带来两个立竿见影的好处第一调平衡不用改代码改完配置重启就能看到效果第二你开始用“数据驱动”的视角看游戏而不是“代码即规则”。我自己的习惯是每改完一组参数就记录一下当时的体验感受比如“TRAFFIC_SPEED5速度适中但死亡反馈太轻”。下次拿到别人的 python 赛车小游戏源代码我会第一时间找它的参数表而不是先去读主循环。因为参数表才是作者对游戏手感理解的浓缩读懂了它这个游戏能被改成什么程度、值得投入多少时间基本就清楚了。逆行飙车这个项目最值钱的部分其实不是“能跑”而是它让你亲手摸到游戏设计里风险与收益怎么互相制衡。当你能解释清楚为什么逆行道权重是 0.5 而不是 0.9为什么计分要乘 2 而不是加 10你就已经超出了“照着抄代码”的阶段。哪怕你只是把它改成了双人分屏、加了一个漂移按键甚至只把难度曲线调成一局五分钟左右那都是你自己的作品了。希望这份代码和踩坑记录能帮你少走几步弯路也希望你把调参得到的经验再传回给下一位拿着它源码的人。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?