Python+pygame飞机大战:从零跑通射击游戏完整代码
发布时间:2026/10/31 7:48:54 锦皓数字建站

简介这是一份面向初学者的 j2ME 手机射击游戏源码以飞机为主角代码结构与逻辑相对基础适合想入门移动游戏开发、理解游戏循环与精灵机制的学习者动手实践。压缩包共 200 个文件约 4.27MB包含 11 个 java 源文件、22 个 class 编译文件、138 个 png 图像素材、20 个 mid 音效以及 jad、jar 等打包配置覆盖源码、资源与可运行产物便于直接导入 Eclipse 环境调试。资源围绕游戏循环、飞机与敌机精灵、矩形碰撞检测、按键输入处理、Graphics 图形渲染、音效播放和多状态管理等核心知识点展开读者可据此掌握 j2ME 下 MIDP 与 CLDC 的基本用法并参考其中的性能优化与资源管理思路。目前已有 1951 人学习下载适合作为课程设计或自学练手项目。1. 一款简单射击类游戏代码从零跑通飞机大战的完整落地路径前阵子帮一个刚学完 Python 基础的朋友看代码他卡在一个飞机射击小游戏上——子弹打出去不消失、敌机撞过来没反应、分数永远停在 0。翻完源码我发现这类「简单射击类游戏」的坑几乎都集中在同一批地方游戏循环的帧同步、碰撞检测的判定顺序、精灵组的增删时机。这份飞机射击游戏代码正好把这些环节都摊开写了一遍没有引擎黑盒纯靠循环和坐标运算撑起整个玩法。它适合两类人一是想拿一个能跑起来的小项目练手、理解游戏主循环到底怎么转的新手二是想找个干净底子做二次开发、加道具加关卡的老手。下面我按「能干什么 → 怎么跑 → 核心怎么实现 → 怎么改」的顺序拆一遍。2. 运行环境与项目结构先把这份代码跑起来2.1 技术栈与依赖判断这份代码是典型的 Python pygame 组合。pygame 的好处是轻装一个包就能跑不需要配引擎、不需要资源导入流程窗口、绘图、事件、声音全在一套 API 里。对射击游戏来说它提供的Sprite和Group两个类刚好覆盖了「一堆会动的东西」这个核心需求——玩家飞机、敌机、子弹本质上都是精灵统一更新、统一绘制、统一做碰撞代码量能压得很低。常见做法是项目根目录放一个入口脚本资源飞机图、子弹图、背景图、音效单独放assets/或images/目录。依赖就一行pip install pygame装完用python -c import pygame; print(pygame.ver)验证一下版本能打印出来就说明环境通了。如果用的是虚拟环境记得先激活再装不然跑的时候会报ModuleNotFoundError: No module named pygame这是新手最高频的一类翻车。2.2 目录结构长什么样这类项目的目录一般不会太深我按常见组织方式给你一个参照实际文件名以你手上的源码为准plane-shooter/ ├── main.py # 入口游戏主循环 ├── settings.py # 屏幕尺寸、帧率、速度等常量 ├── player.py # 玩家飞机类 ├── enemy.py # 敌机类与生成逻辑 ├── bullet.py # 子弹类 ├── assets/ │ ├── images/ # 飞机、子弹、背景图 │ └── sounds/ # 射击、爆炸音效 └── requirements.txt # 依赖清单把常量单独抽到settings.py是个好习惯屏幕宽高、帧率、各类速度都集中在一处后面调手感不用满项目找数字。如果你的源码是单文件版本所有类都堆在main.py里也不影响运行只是改起来会累一点建议先按上面的结构拆一遍再动手。2.3 启动与第一个可运行画面入口脚本的核心就是初始化、建窗口、进循环。下面这段是几乎所有 pygame 项目的骨架你对照源码看逻辑一定对得上import pygame import sys from settings import WIDTH, HEIGHT, FPS pygame.init() # 初始化所有 pygame 子系统 screen pygame.display.set_mode((WIDTH, HEIGHT)) # 创建游戏窗口 pygame.display.set_caption(Plane Shooter) clock pygame.time.Clock() # 帧率控制器 running True while running: clock.tick(FPS) # 把循环锁在固定帧率 for event in pygame.event.get(): # 取出本帧所有事件 if event.type pygame.QUIT: running False screen.fill((0, 0, 0)) # 清屏避免残影 # 这里更新和绘制所有精灵 pygame.display.flip() # 把画好的内容翻到屏幕上 pygame.quit() sys.exit()clock.tick(FPS)这行是关键它让循环每帧等待到固定间隔游戏速度才不会随机器性能忽快忽慢。screen.fill每帧清屏也不能省否则上一帧的飞机图像会留在屏幕上拖出残影。pygame.display.flip()负责把后台绘制的内容一次性刷到前台少了它你只会看到一个黑窗口。跑通这段骨架后面所有玩法都是往循环里填内容。3. 核心模块拆解一个射击游戏的主循环与精灵系统3.1 游戏主循环更新与绘制分离射击游戏的主循环有个固定套路先处理输入再更新所有对象状态最后统一绘制。这个顺序不能乱先更新后绘制才能保证画面反映的是最新位置。下面是一个带精灵组的主循环写法all_sprites pygame.sprite.Group() # 所有需要更新和绘制的对象 enemies pygame.sprite.Group() # 敌机单独一组方便碰撞和清理 bullets pygame.sprite.Group() # 子弹单独一组 while running: clock.tick(FPS) for event in pygame.event.get(): if event.type pygame.QUIT: running False all_sprites.update() # 每个精灵自己实现 update 方法 screen.fill((0, 0, 0)) all_sprites.draw(screen) # 组内所有精灵依次绘制 pygame.display.flip()all_sprites.update()会遍历组内每个精灵调用它的update()所以每个类只要把自己的移动逻辑写进update主循环就不用关心具体是谁。all_sprites.draw(screen)同理统一绘制。这种「组管理」的思路是 pygame 做射击游戏最省事的地方新增一种对象只要加进组里循环一行都不用改。3.2 玩家飞机键盘输入与边界约束玩家飞机的核心是响应方向键、限制在屏幕内、按空格发射子弹。下面这段把三件事都写全了class Player(pygame.sprite.Sprite): def __init__(self, bullets_group): super().__init__() self.image pygame.image.load(assets/images/player.png).convert_alpha() self.rect self.image.get_rect() self.rect.center (WIDTH // 2, HEIGHT - 80) # 初始位置在底部中间 self.speed 6 self.bullets bullets_group self.shoot_cooldown 0 # 射击冷却计数防止一帧连发 def update(self): keys pygame.key.get_pressed() # 取当前按键状态支持长按 if keys[pygame.K_LEFT]: self.rect.x - self.speed if keys[pygame.K_RIGHT]: self.rect.x self.speed if keys[pygame.K_UP]: self.rect.y - self.speed if keys[pygame.K_DOWN]: self.rect.y self.speed # 边界约束防止飞出屏幕 self.rect.left max(self.rect.left, 0) self.rect.right min(self.rect.right, WIDTH) self.rect.top max(self.rect.top, 0) self.rect.bottom min(self.rect.bottom, HEIGHT) if self.shoot_cooldown 0: self.shoot_cooldown - 1 if keys[pygame.K_SPACE] and self.shoot_cooldown 0: self.bullets.add(Bullet(self.rect.midtop)) self.shoot_cooldown 10 # 冷却 10 帧控制射速pygame.key.get_pressed()和事件循环里的KEYDOWN不一样它返回的是「此刻哪些键按着」适合做持续移动。边界约束用max/min夹住rect的四条边比写四个 if 判断更简洁。shoot_cooldown是控制射速的常用手法不加的话按住空格每帧都会生成子弹射速直接失控这也是很多人第一次跑起来觉得「子弹太密」的原因。3.3 子弹与敌机生命周期和自动清理子弹和敌机都是「生成后自己往上/往下飞飞出屏幕就销毁」的对象。销毁这一步必须做否则精灵组会无限膨胀跑几分钟就卡成幻灯片class Bullet(pygame.sprite.Sprite): def __init__(self, pos): super().__init__() self.image pygame.image.load(assets/images/bullet.png).convert_alpha() self.rect self.image.get_rect(midbottompos) self.speed 10 def update(self): self.rect.y - self.speed # 子弹向上飞 if self.rect.bottom 0: # 飞出屏幕顶部 self.kill() # 从所有组中移除自己 class Enemy(pygame.sprite.Sprite): def __init__(self, pos): super().__init__() self.image pygame.image.load(assets/images/enemy.png).convert_alpha() self.rect self.image.get_rect(midtoppos) self.speed 3 def update(self): self.rect.y self.speed # 敌机向下飞 if self.rect.top HEIGHT: # 飞出屏幕底部 self.kill()self.kill()是 pygame 精灵的内置方法调用后它会自动从所有加入过的组里移除不需要你手动remove。这个机制配合「飞出边界就 kill」的写法就是精灵生命周期的标准闭环。敌机生成一般用定时器控制常见做法是在主循环里累加一个计数器每满 N 帧往enemies组里加一架位置在屏幕顶部随机横坐标。3.4 碰撞检测分组判定与命中处理碰撞是射击游戏的判定核心pygame 提供spritecollide和groupcollide两种。子弹打敌机用组对组玩家撞敌机用单个对组# 子弹与敌机碰撞两个都销毁加分 hits pygame.sprite.groupcollide(bullets, enemies, True, True) if hits: score len(hits) * 10 # 玩家与敌机碰撞玩家扣血或结束敌机销毁 if pygame.sprite.spritecollide(player, enemies, True): player_hp - 1 if player_hp 0: running Falsegroupcollide(bullets, enemies, True, True)后两个参数分别表示「碰撞后是否销毁第一组的精灵」和「是否销毁第二组的精灵」都传True就是子弹和敌机同归于尽。spritecollide(player, enemies, True)的第三个参数表示碰撞到的敌机是否销毁。这里有个顺序讲究先判子弹打敌机再判玩家撞敌机否则同一帧里敌机可能先撞到玩家被销毁子弹的命中就丢了。碰撞判定放在update之后、draw之前保证画面和判定用的是同一帧的位置。4. 避坑与排查跑这份代码最容易翻车的几个点4.1 图片加载报错或画面全黑现象是启动直接抛FileNotFoundError或者窗口出来了但飞机看不见。原因通常是资源路径写的是相对当前工作目录而你从别的目录启动脚本路径就对不上了画面全黑则多半是图片没调convert_alpha()透明通道没处理。解决办法是用os.path.join(os.path.dirname(__file__), assets/images/xxx.png)拼绝对路径加载后统一加.convert_alpha()。我一般会在初始化后先打印一次资源目录是否存在省得后面一个个猜。4.2 子弹越打越卡、帧率往下掉现象是玩一两分钟后明显变卡。原因基本是子弹飞出屏幕没kill()精灵组里堆了几千个已经看不见的对象每帧还在遍历更新和绘制。解决就是给每个会飞出边界的对象在update里加边界判断和self.kill()。排查时可以临时打印len(bullets)正常应该稳定在个位数到几十如果一直涨就是没清理。4.3 按住空格射速失控现象是子弹密得像激光。原因是发射逻辑写在事件循环的KEYDOWN里或者虽然用了get_pressed但没加冷却。KEYDOWN在按住时系统会重复触发get_pressed则是每帧都返回按下状态。解决是加一个冷却计数器每帧递减归零才允许发射射速就稳了。这个参数直接决定手感10 帧左右是比较常见的起始值。4.4 碰撞判定「打中了却没反应」现象是肉眼看到子弹穿过敌机但分数没加。原因通常是判定顺序问题或者碰撞检测写在了update之前用的是上一帧的旧位置。还有一种情况是rect和image尺寸不匹配碰撞框比图小。解决是把碰撞检测放在所有update之后、draw之前并确认self.rect self.image.get_rect()是在图片加载之后设置的。排查时可以把rect用pygame.draw.rect画出来看碰撞框对不对一眼就知道。4.5 窗口关不掉或退出报错现象是点关闭按钮没反应或者退出时抛异常。原因是事件循环里没处理pygame.QUIT或者退出时没调pygame.quit()就sys.exit()。解决是确保事件循环里有QUIT分支把running置False循环结束后先pygame.quit()再退出。这个坑不大但很烦尤其是调试时反复开关窗口。5. 二次开发改造从能跑到好玩的关键几步把这份代码跑通只是起点真正有意思的是往上加东西。我按改动成本从低到高给你几条路子都是这类射击游戏最常被加的功能。第一条是敌机种类和掉落。现在敌机是单一类型匀速下落你可以给Enemy加一个hp属性和type参数血厚的敌机要打两三下才死死亡时按概率掉道具。道具本身也是个精灵玩家碰到就触发效果比如加射速、加护盾、清屏炸弹。改动集中在Enemy.update和碰撞处理里主循环几乎不用动。第二条是难度曲线。固定生成间隔玩久了会腻常见做法是让生成间隔随分数或时间递减敌机速度也缓慢提升。可以在主循环里维护一个spawn_interval变量每过一段时间减一帧下限设个值防止难度爆炸。这个改动很小但体感提升明显。第三条是状态机。现在游戏只有「进行中」和「结束」两个状态加个开始界面和暂停功能会让项目完整很多。用一个game_state变量在menu / playing / paused / gameover之间切换主循环里按状态决定更新哪些组、绘制哪些界面。下面是个简化写法game_state menu while running: clock.tick(FPS) for event in pygame.event.get(): if event.type pygame.QUIT: running False if event.type pygame.KEYDOWN: if game_state menu and event.key pygame.K_RETURN: game_state playing elif game_state playing and event.key pygame.K_p: game_state paused elif game_state paused and event.key pygame.K_p: game_state playing if game_state playing: all_sprites.update() # 碰撞检测、生成敌机等逻辑 # 绘制部分按状态分支game_state这个变量就是整个游戏的总开关所有更新和绘制都挂在它下面加界面不用改原有逻辑只是多几个分支。这是我从一个反复重构的项目里学到的状态机要早点加等逻辑堆多了再抽改动面会大到想重写。第四条是数据外置。把敌机速度、生成间隔、子弹伤害这些数值从代码里挪到settings.py甚至一个 JSON 文件调平衡时不用改代码重跑。我一般会先做这一步再调难度不然每次改个数字都要翻半天文件。验证改动有没有生效最直接的办法是加一个调试模式屏幕上实时打印帧率、当前精灵数量、分数和状态。pygame.font几行就能画出来比反复猜快得多。改完一个功能就跑一遍完整流程——开始、玩、暂停、结束、重开确认没有状态残留。从那以后我每次动这类游戏的主循环都强制先把状态机和调试信息加好再写新功能血泪经验是没有调试输出的射击游戏改起来全靠玄学。希望这份拆解能帮你把这份代码真正跑起来、改出自己的版本。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。