资讯动态

用Python和Pygame实现飞行器着陆模拟:零基础编程教学项目

发布时间:2026/10/14 19:56:52 来源:尧图企业网站定制
“Apollo-11”这个项目名是我在带社团时随手起的。当时想找一个能让零基础学生迅速看到“代码改变世界”的载体不要一上来就是数据库、网络编程那种抽象的东西最好能跑起来能看见东西动能调参数看效果。翻来覆去最后落地到Python Pygame做飞行器着陆模拟。这个项目不复杂核心就三个词重力、推力、燃料管理。做完之后学生对游戏里“物体为什么会动”“为什么按键有反应”这些概念会有真正体感而且代码量控制在几百行不会劝退新手。这篇文章按最容易复现的顺序来讲先聊整体设计再拆物理参数然后给完整实现最后整理我在调参和排错时踩过的坑。所有代码我尽量贴全拿过去装上依赖就能跑。适合的信息技术课、科技节展示、编程社团热身项目或者单纯想练Python游戏编程的初学者都能直接用上。1. 整体设计与思路拆解1.1 核心目标让参数变化“看得见”编程教育里有一个老问题变量、循环、条件分支这些语法学生背了就会但不知道有什么用。Apollo-11这个项目最大的价值是把这些语法塞进一个能“实时反馈”的物理模拟场景里。学生按着空格键看到燃料条下降、速度数字变化、飞行器姿态改变自然就理解了“代码就是指令”“变量是状态”“循环是持续检测”这些概念。我把整个项目拆成三个模块飞行器状态模块负责记录高度、速度、燃料余量等数据。物理更新模块每帧根据重力、推力、燃料状态计算新的速度和位置。界面渲染模块绘制飞行器、地面、速度表、燃料条。这三个模块各自独立在代码里用函数区分得很清楚。设计这种结构是为了让初学者养成“先拆解再实现”的习惯而不是一团乱码堆到底。1.2 为什么用Pygame而不是游戏引擎有人问Unity、Godot做这种小游戏不是更简单吗确实引擎里自带的物理系统可能半自动就搞定了但那是“黑盒”。我们的目标不是做游戏而是学编程和物理。Pygame把底层窗口、事件、像素操作暴露出来学生能清楚看到“一帧”是怎么循环的一个坐标是怎么被几行计算改变的。另外还有一个很现实的原因部署环境。多数学校机房装的是Python 3.8或3.9Pygame用pip一条命令就能装上不用申请软件安装权限也不吃显卡。对我来说能在一节课内让学生跑起来比动画效果炫酷重要得多。1.3 状态机项目的主线逻辑飞行器不能永远只用一套逻辑。不同阶段它的行为完全不同。这里我用最简单的状态机来控制飞行中重力生效按空格施加推力。已着陆停止物理更新显示结算信息。硬着陆直接提示任务失败进入重置等待。状态机的好处是让逻辑分支清晰。写代码的时候最怕的就是十几个if互相嵌套互相影响。Apollo-11只有三个状态每个状态只有一条主线哪怕学生中途把代码改乱了也能顺着状态找到问题在哪。2. 核心逻辑与物理参数解析2.1 运动学方程的取舍真正的飞行器着陆涉及刚体动力学、姿态控制、空气阻力非常复杂。但教学项目不需要做到那个程度。我采用的是一维简化运动模型只关注垂直方向上的高度变化。每帧的更新规则速度 速度 重力加速度 - 推力加速度× 帧时间高度 高度 速度 × 帧时间这里的帧时间用dt表示单位是秒。很多人会忽略dt直接用“每帧速度加上固定值”这样程序在不同帧率下表现完全不同60帧和144帧的难度天差地别。为了让参数可复现我把所有物理量都按“每秒”来定义再乘上每帧经过的真实时间。2.2 推力与燃料的平衡设计推力是本项目最需要调的核心参数。我给的初始值是这样的参数数值说明重力加速度120 px/s²模拟“每帧往下的加速度”推力加速度210 px/s²按住空格时的向上加速度初始燃料100%不限制的话这个项目就没难度了燃料消耗速率12% / 秒配合推力大概能持续推8秒左右安全着陆速度不高于55 px/s超过这个值算硬着陆这个配置有个很关键的意图推力只比重力大一点。如果数值差距过大比如推力是重力的三倍学生随便按按就能飞上去感觉不到“管理资源”的紧张感。210对120的设计让推力的净效果是90 px/s²的向上加速度既不迟钝也不粗暴刚好适合新手。2.3 落地判定不能只看“碰到”许多初学者写碰撞检测就是判断飞行器坐标和地面坐标是否相等。这在逻辑上没错但在实际运行中由于每帧位移是离散的飞行器可能从地面“穿透”过去根本检测不到。这个问题的本质是物理更新的步长和碰撞检测的粒度不一致。我采用的方案是先判断下一帧位置是否低于地面。如果已经进入地面回退到地面位置。再检查此时速度是否在安全范围。用“预测-修正”的思路而不是“事后检查”可以避免飞行器穿模也让学生在接触碰撞检测时就建立起边界防护的思维方式。3. 实操过程与核心环节实现3.1 搭建工程目录与初始化环境我习惯给每个小项目建一个干净的目录结构apollo-11/ main.py requirements.txt assets/ font/ sounds/main.py放主程序requirements.txt列依赖。虽然这个项目只有一个文件也能跑但提前养成工程化习惯后面做更复杂的项目时就能顺理成章地进入模块化开发。环境初始化只需要两条命令python -m venv venv venv\Scripts\activate # Windows # 或 source venv/bin/activate # macOS/Linux pip install pygamePygame目前对Python 3.8到3.12都支持良好不用担心版本兼容问题。装完之后可以快速验证一下import pygame print(pygame.version.ver)能打印出版本号就说明环境没问题。很多初学者卡在“安装”这一步其实绝大多数是没激活虚拟环境或者pip版本太老升级一下pip基本能解决。3.2 主循环与关键实现代码接下来给一份可以直接运行的参考实现。我尽量精简保留核心逻辑方便初学者对照阅读。import pygame import sys WIDTH, HEIGHT 800, 600 FPS 60 GRAVITY 120.0 # 重力加速度单位 px/s^2 THRUST 210.0 # 推力加速度单位 px/s^2 FUEL_RATE 12.0 # 每秒消耗燃料百分比 MAX_SAFE_SPEED 55.0 # 最大安全着陆速度单位 px/s GROUND_Y HEIGHT - 40 SHIP_SIZE (12, 24) pygame.init() screen pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption(Apollo-11 Landing Simulation) clock pygame.time.Clock() font pygame.font.SysFont(microsoftyahei, 22) # 初始状态 x_pos WIDTH // 2 y_pos 120.0 vy 0.0 fuel 100.0 landed False game_over False def reset_game(): global x_pos, y_pos, vy, fuel, landed, game_over x_pos WIDTH // 2 y_pos 120.0 vy 0.0 fuel 100.0 landed False game_over False def draw_ui(): speed_text font.render(fSpeed: {vy:6.1f} px/s, True, (255, 255, 255)) fuel_text font.render(fFuel: {fuel:5.1f} %, True, (255, 255, 255)) screen.blit(speed_text, (20, 20)) screen.blit(fuel_text, (20, 50)) if landed: tip font.render(Landed! Press R to restart, True, (0, 255, 0)) screen.blit(tip, (WIDTH // 2 - 120, HEIGHT // 2)) elif fuel 0: tip font.render(Out of fuel! Press R to restart, True, (255, 80, 80)) screen.blit(tip, (WIDTH // 2 - 140, HEIGHT // 2)) while True: dt clock.tick(FPS) / 1000.0 for event in pygame.event.get(): if event.type pygame.QUIT: pygame.quit() sys.exit() if event.type pygame.KEYDOWN: if event.key pygame.K_r: reset_game() keys pygame.key.get_pressed() thrusting keys[pygame.K_SPACE] and fuel 0 and not landed if thrusting: vy (GRAVITY - THRUST) * dt fuel - FUEL_RATE * dt if fuel 0: fuel 0.0 else: vy GRAVITY * dt y_pos vy * dt # 越界修正 着陆判定 if y_pos GROUND_Y: y_pos GROUND_Y if abs(vy) MAX_SAFE_SPEED: landed True vy 0.0 else: vy 0.0 game_over True # 绘制 screen.fill((15, 18, 26)) pygame.draw.rect(screen, (80, 80, 80), (0, GROUND_Y, WIDTH, HEIGHT - GROUND_Y)) # 飞行器用一个小矩形来表示 ship_rect pygame.Rect(0, 0, SHIP_SIZE[0], SHIP_SIZE[1]) ship_rect.center (x_pos, int(y_pos)) pygame.draw.rect(screen, (230, 180, 60), ship_rect) if thrusting: # 画一个简易尾焰 pygame.draw.circle(screen, (255, 120, 30), (x_pos, int(y_pos) 16), 8) draw_ui() pygame.display.flip()这段代码能跑但只是一个“骨架版本”。真正到我给学生用的版本里还加了燃料耗尽提醒音效、渐变背景、燃料条进度条、历史最佳成绩记录等。不过骨架已经有足够的教学价值。3.3 几个需要留意的细节代码里最容易让新手困惑的是dt。Pygame的clock.tick(FPS)返回的是上一帧耗时单位是毫秒所以要除以1000换算成秒。如果漏掉这个换算物理模拟会快得离谱飞船一秒钟就冲上天。另一个容易犯的错是“相对坐标”思维混乱。Pygame窗口的y轴是向下生长的所以速度为正代表向下飞为负代表向上飞。这跟物理课上常见的向上为正不太一样。我只用了vy这一个变量在UI里显示速度时通常会再转成“向上为正”来展示避免学生理解偏差。3.4 调参方法与数值感知训练我在课上让学生做的最有意思的练习不是直接给最终参数而是让他们自己调一遍。比如把重力改成200其他不变看飞机会怎样。把推力改成只比重力大20看燃料消耗速度是否合理。把安全着陆速度改成150感受“看似安全但其实不安全”的边界。每次调整后学生都要记录自己的观察。这样一轮下来他们对“参数是程序的一部分”这件事就有了直观认识。Apollo-11真正的收获不是代码本身而是对“模型近似”的理解真实物理不是这样但合理简化后可以用几个参数抓住问题的主要矛盾。4. 常见问题与排查技巧实录4.1 飞船莫名其妙穿过地面这是最多人遇到的问题我在前文已经解释了原因位置更新和碰撞检测在同一帧里按顺序执行如果速度过快一帧的位移超过地面到飞行器的距离就检测不到碰撞了。除了用“预测-修正”的方式处理还有一个补救手段把最大位移限制在地面高度的1/3之内。比如每帧位移大于一定值就拆成多个小步来更新。但这样会引入更复杂的逻辑初学者阶段不推荐。优先用回退加判定简单稳定。4.2 画面忽快忽慢物理不一致表现是在配置好的电脑上正常换到差一点的电脑上飞船下落速度好像没变但操作明显变“飘”了。原因是把dt写死成1/60实际帧率低于60的时候物理模拟的步长仍然是固定值导致画面和逻辑脱节。Pygame里有一个标准解法用clock.tick(FPS) / 1000拿到真实帧时间再把它传递给所有与时间相关的计算。这个细节看似微小但直接影响项目质量。如果确实想让行为完全一致可以再做一层“固定时间步长累积更新”但小项目别过度设计。4.3 中文字体显示成方块Pygame的默认字体不支持中文So你直接用SysFont(microsoftyahei, 22)在部分环境里会失效。最稳妥的方案是下载一个开源中文字体文件放到assets/font/然后font pygame.font.Font(assets/font/AlibabaPuHuiTi-Regular.ttf, 22)Font方法在字体文件不存在时会抛异常比SysFont静默替换成默认字体更容易发现问题。这属于典型的“排错友好”设计宁可报错也不要默默渲染出不可用的界面。4.4 启动慢、窗口不响应有些机器上Pygame窗口启动后无响应多半是杀毒软件拦截或者显卡驱动太老。应急处理一般是换pygame.display.set_mode的参数比如加上pygame.RESIZABLE让窗口模式更标准。再不行降低分辨率到 640×480。还有一个非常容易忽略的点主循环里如果某个操作耗时过长比如每帧都读取一次文件去播放音效帧率会暴跌表现就像“卡死”。我一般会把音效播放限制在事件触发时而不是每帧检测后播放。4.5 常见问题速查表现象可能原因解决方案飞船瞬间坠落忘记乘dt或单位错误检查时间和加速度单位是否统一按键没反应事件循环被阻塞确认pygame.event.get()每帧都被调用燃料数字跳变没有做最小值截断燃料低于0时强制归零程序闪退字体文件缺失用pygame.font.Font加载本地字体画面撕裂垂直同步没开设置clock.tick或用pygame.display.flip代替update速度显示异常y轴方向理解错了在显示时对vy取反并注释原因这些坑我基本都踩过。尤其是字体问题第一次带学生在机房跑一运行全窗口都是方块场面相当尴尬。后来就养成了“所有资源文件一律本地化”的习惯不依赖任何系统环境到哪里都能跑。5. 项目扩展方向与实际体会基础版做出来后我很推荐给项目加上这些扩展燃料余量的进度条显示用简单的比例矩形绘制。水平移动和风向扰动让操作维度更丰富。一个简单的“历史成绩”文件保存最近几次着陆速度形成目标感。添加倒计时或任务目标例如“在燃料耗尽前进入着陆区”。以我实际带课的经验来看Apollo-11最合适的节奏是两节课。第一节课实现垂直下落和按键控制第二节课调试参数并加入燃料限制。两节课后大部分学生能自己改出不同的难度模式比如“月球模式”重力更大、燃料更少和“练习模式”重力更小、安全速度更宽。最后分享一个调参的小技巧把常用的几个物理参数做成界面上的可调项而不是每次修改代码。这一步不复杂用一个pygame滑块或者直接用键盘左右键调整即可。学生可以一边玩一边改数值立刻看到模拟结果变化理解和记忆都会深刻很多。我自己在调试时也依赖这套方法比改代码重启速度快得多而且不容易把参数改乱。Apollo-11这个项目特意没有做得很花哨画面简单逻辑清晰代码量也小。它的隐藏价值在于让你熟悉“状态、重力、推力、燃料、判定”这一整套游戏物理的基本流程。做完之后再去接触复杂游戏框架起码知道底层是在做什么不会再被玩具引擎和教程牵着走。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑