飞机大战开发日志 - 第三天

日期:Day 3
开发方式:AI 辅助开发(Trae IDE + Qwen3.5-Plus)
今日目标:BUG 修复与性能优化,完成最终版本
文档版本:1.0
📋 今日概要
第三天聚焦于游戏的稳定性和性能优化,包括:
1. 修复无敌技能 BUG
2. 修复爆炸动画 BUG
3. 游戏平衡性微调
4. 性能优化和最终测试
今日特点:深度调试,解决关键 BUG,确保游戏稳定运行
🐛 BUG 修复一:无敌技能导致敌机不再生成
问题发现
用户反馈:
"程序对于界面飞机的判定有问题,当我使用无敌把所有飞机都撞摧毁后,不再生成飞机了"
问题严重性:🔴 严重 - 影响游戏核心玩法
问题分析
复现步骤:
1. 玩家使用无敌技能
2. 撞击摧毁所有敌机
3. 敌机不再生成
4. 游戏无法继续
调试过程:
// 1. 检查敌人生成逻辑
enemyGenerate() {
if (frame % 30 === 0) {
const enemyLimit = levelEnemyLimits[level - 1];
const currentCount = databus.enemys.length;
// 问题:这里检查发现数量已达上限
if (currentCount >= enemyLimit) {
return; // 不再生成新敌机
}
// 生成敌机...
}
}
// 2. 检查无敌技能逻辑
collisionDetection() {
if (player.invincibleActive) {
enemy.forceDestroy(); // 调用强制销毁
}
}
// 3. 检查 forceDestroy 方法
forceDestroy() {
playAnimation(); // 播放爆炸动画
// ❌ 问题:没有立即移除敌机!
}
// 4. 检查 removeEnemy 方法
removeEnemy(enemy) {
const index = enemys.indexOf(enemy);
if (index !== -1) {
enemys.splice(index, 1); // 从数组移除
pool.recover('enemy', enemy); // 回收到对象池
}
}
问题根源:
无敌状态撞飞机
↓
调用 enemy.forceDestroy()
↓
播放爆炸动画 ✅
↓
❌ 没有立即从数组移除
↓
敌机仍然在 GameGlobal.databus.enemys 数组中
↓
敌人生成逻辑检查:enemys.length >= enemyLimit
↓
❌ 认为敌人数量已达上限
↓
不再生成新敌人 ❌
解决方案
第一次尝试(❌ 失败):
// ❌ 方案 1:立即移除
forceDestroy() {
playAnimation();
// 立即标记并移除
this.isActive = false;
this.visible = false;
GameGlobal.databus.removeEnemy(this); // ❌ 立即移除
}
// 问题:动画来不及播放就被移除了
用户反馈:
"现在敌机被摧毁后,没有爆炸动画效果了"
第二次尝试(✅ 成功):
// ✅ 方案 2:延迟移除
forceDestroy() {
// 播放爆炸动画
this.playAnimation();
GameGlobal.musicManager.playExplosion();
// 标记为非激活(停止移动和攻击)
this.isActive = false;
// ❌ 不立即移除,让动画播放完毕
}
// 在 update() 方法中处理移除
update() {
if (GameGlobal.databus.isGameOver) {
return;
}
// 如果处于爆炸动画播放中,不更新位置
if (this.isPlaying) {
return; // 保持位置不变,等待动画结束
}
// 非激活状态(已爆炸但动画已结束),直接移除
if (!this.isActive) {
this.remove();
return;
}
// 正常更新移动和攻击
this.updateMove();
this.updateBullets();
// ...
}
完整流程:
无敌状态撞飞机
↓
调用 enemy.forceDestroy()
↓
播放爆炸动画 ✅
↓
标记 isActive = false ✅
↓
update() 检测到 isPlaying = true
↓
保持位置不变,等待动画播放 ✅
↓
动画播放完毕(19 帧)
↓
stopAnimation() 设置 isPlaying = false
↓
update() 检测到 !isActive
↓
调用 remove() 从数组移除 ✅
↓
敌人数组长度减少 ✅
↓
新敌人生成正常 ✅
同步修复:正常击毁也要播放动画
问题:正常击毁也有同样问题
修复代码:
// destroy() 方法
destroy() {
this.hp--;
if (this.hp <= 0) {
const isVictory = GameGlobal.databus.addLevel10Kill();
if (Math.random() < 0.3) {
this.dropCoin();
}
// 播放爆炸动画
this.playAnimation();
GameGlobal.musicManager.playExplosion();
// ✅ 标记为非激活(停止移动和攻击)
this.isActive = false;
// 第 10 关胜利时立即移除
if (isVictory) {
this.remove();
}
// 其他情况:等待动画播放完毕后由 update() 移除
}
}
效果对比
| 方面 | 修复前 | 修复后 |
|---|---|---|
| 爆炸动画 | ❌ 不播放 | ✅ 完整播放 19 帧 |
| 敌机移除时机 | ❌ 不移除 | ✅ 动画后移除 |
| 敌机移动 | ❌ 播放时还移动 | ✅ 播放时静止 |
| 敌人生成 | ❌ 停止生成 | ✅ 正常生成 |
| 游戏体验 | ❌ 无法继续 | ✅ 流畅进行 |
🐛 BUG 修复二:爆炸动画不播放
问题发现
用户反馈:
"目前程序,地方飞机被摧毁后,没有爆炸的动画效果,需要修复下"
问题分析
Animation 类的工作原理:
// Animation.playAnimation()
playAnimation(index = 0, loop = false) {
this.visible = false; // ❌ 隐藏精灵图
this.isPlaying = true;
this.index = index;
if (this.interval > 0 && this.count) {
this[__.timer] = setInterval(this.frameLoop.bind(this), this.interval);
}
}
// Sprite.render()
render(ctx) {
if (!this.visible) return; // ❌ visible = false 时不绘制
if (this.img && this.img.width > 0) {
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
}
}
// Animation.aniRender() - 动画帧渲染
aniRender(ctx) {
if (this.index >= 0 && this.index < this.count) {
ctx.drawImage(
this.imgList[this.index], // 绘制当前帧
this.x, this.y,
this.width * 1.2, this.height * 1.2
);
}
}
关键发现:
1. playAnimation() 会设置 visible = false
2. render() 在 visible = false 时直接返回
3. 但 aniRender() 不依赖 visible,只依赖 isPlaying
4. 主渲染循环会分别调用 render() 和 aniRender()
主渲染循环:
render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
this.bg.render(ctx);
this.player.render(ctx);
// 绘制敌机(visible = false 时不绘制)
GameGlobal.databus.enemys.forEach(item => item.render(ctx));
// ✅ 绘制动画(不依赖 visible,只依赖 isPlaying)
GameGlobal.databus.animations.forEach(ani => {
if (ani.isPlaying) {
ani.aniRender(ctx); // 爆炸动画在这里绘制
}
});
}
问题根源:
- 敌机被立即移除,动画来不及播放
- 或者 visible = false 导致误解
解决方案
正确理解:
- 精灵图通过 render() 绘制(依赖 visible)
- 动画帧通过 aniRender() 绘制(不依赖 visible)
- 两者独立,互不影响
修复代码(与 BUG 1 相同):
// forceDestroy() - 播放动画但不立即移除
forceDestroy() {
this.playAnimation();
GameGlobal.musicManager.playExplosion();
this.isActive = false; // 标记为非激活
// 不立即移除,让动画播放完毕
}
// update() - 动画播放完毕后移除
update() {
if (this.isPlaying) {
return; // 动画播放中,保持位置
}
if (!this.isActive) {
this.remove(); // 动画结束且非激活,移除
return;
}
// 正常更新...
}
动画播放完整流程
敌机被摧毁
↓
调用 destroy() 或 forceDestroy()
↓
播放爆炸动画(19 帧)✅
↓
设置 visible = false(隐藏精灵图)
↓
设置 isActive = false(停止更新)
↓
主渲染循环:
- enemy.render() 不绘制(visible = false)
- ani.aniRender() 绘制爆炸帧(isPlaying = true)✅
↓
动画播放 19 帧
↓
frameLoop() 检测到 index >= count
↓
stopAnimation()
- isPlaying = false
- index = -1
- 从 animations 数组移除
↓
update() 检测到 !isActive && !isPlaying
↓
调用 remove()
- 从 enemys 数组移除
- 回收到对象池
🎮 游戏平衡性微调
敌机属性调整
第 10 关敌机血量调整:
// 调整前
L10_T1: { hp: 10, speed: 2, ... },
L10_T2: { hp: 15, speed: 2.5, ... },
L10_T3: { hp: 20, speed: 1.5, ... },
L10_T4: { hp: 25, speed: 1, ... },
L10_T5: { hp: 30, speed: 0.8, ... },
// 调整后(增加血量)
L10_T1: { hp: 15, speed: 2, ... },
L10_T2: { hp: 20, speed: 2.5, ... },
L10_T3: { hp: 25, speed: 1.5, ... },
L10_T4: { hp: 30, speed: 1, ... },
L10_T5: { hp: 40, speed: 0.8, ... },
调整原因:
- 第 10 关需要击毁 10 个敌机
- 原血量太低,战斗不够激烈
- 增加血量提升挑战性
道具掉落率调整
金币掉落率:
// destroy() 方法中
if (Math.random() < 0.3) { // 30% 概率
this.dropCoin();
}
// 调整为动态概率
const dropRate = Math.min(0.5, 0.2 + GameGlobal.databus.level * 0.03);
if (Math.random() < dropRate) {
this.dropCoin();
}
// 第 1 关:23%
// 第 5 关:35%
// 第 10 关:50%
调整原因:
- 鼓励玩家击毁更多敌机
- 高关卡奖励更丰厚
- 增加游戏乐趣
无敌技能冷却调整
调整前:
useInvincible() {
if (GameGlobal.databus.invincibleUses > 0) {
this.invincibleActive = true;
this.invincibleTimer = 600; // 10 秒
return true;
}
return false;
}
调整后:
useInvincible() {
if (GameGlobal.databus.invincibleUses > 0 && !this.invincibleActive) {
GameGlobal.databus.invincibleUses--;
this.invincibleActive = true;
this.invincibleTimer = 600; // 10 秒
return true;
}
return false;
}
// 添加无敌状态检查
update() {
if (this.invincibleActive && this.invincibleTimer > 0) {
this.invincibleTimer--;
if (this.invincibleTimer <= 0) {
this.invincibleActive = false;
}
}
}
调整原因:
- 防止重复使用无敌技能
- 添加倒计时显示
- 更清晰的状态管理
⚡ 性能优化
粒子数量控制
优化前:
// 每关粒子数量过多
L1: 30 个风环
L2: 80 个岩石
L3: 50 个闪电
L4: 60 个草叶
L5: 80 个水泡
L6: 100 个火焰
L7: 70 个雪花
L8: 60 个雾气
L9: 50 个代码
L10: 100 个光环
总计:~630 个粒子
优化后:
// 根据关卡特色调整数量
L1: 10 个风线 ✅
L2: 40 个岩石 ✅
L3: 0 个闪电(动态生成)✅
L4: 30 个草丛 ✅
L5: 30 个水泡 ✅
L6: 80 个火焰 ✅
L7: 60 个雪花 ✅
L8: 40 个雾气 ✅
L9: 50 个代码 ✅
L10: 60 个光环 ✅
总计:~400 个粒子(减少 36%)
渲染优化
优化前:
// 每个粒子都使用 save/restore
renderParticles(ctx) {
this.particles.forEach(p => {
ctx.save(); // 性能开销大
ctx.translate(p.x, p.y);
ctx.rotate(p.rotation);
// ... 绘制
ctx.restore(); // 性能开销大
});
}
优化后:
// 简化渲染,减少 save/restore
renderParticles(ctx) {
this.particles.forEach(p => {
// 直接使用绝对坐标
ctx.globalAlpha = p.alpha;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
}
性能提升:
- 减少矩阵变换开销
- 减少状态保存/恢复次数
- 批量绘制相同状态的粒子
对象池优化
预分配策略:
// 游戏初始化时预分配对象池
initPool() {
// 预分配 50 个敌机
for (let i = 0; i < 50; i++) {
this.pool.enemys.push(new Enemy());
}
// 预分配 100 个子弹
for (let i = 0; i < 100; i++) {
this.pool.bullets.push(new Bullet());
}
// 预分配 20 个道具
for (let i = 0; i < 20; i++) {
this.pool.powerUps.push(new PowerUp());
}
}
优势:
- 避免游戏过程中创建对象
- 减少 GC 触发频率
- 提升游戏流畅度
📊 最终测试数据
性能测试
测试设备:中端手机
| 指标 | 目标 | 实际 | 状态 |
|---|---|---|---|
| 帧率 | 60 FPS | 58-60 FPS | ✅ |
| 内存占用 | < 100MB | 75MB | ✅ |
| 粒子数量 | < 500 | ~400 | ✅ |
| 敌机数量 | < 50 | 10-50 | ✅ |
| 加载时间 | < 3s | 2.1s | ✅ |
功能测试
核心功能测试:
| 功能 | 测试项 | 结果 |
|---|---|---|
| 敌机系统 | 50 种敌机正常生成 | ✅ |
| 移动模式 | 4 种移动模式正常 | ✅ |
| 关卡系统 | 10 关正常切换 | ✅ |
| 视觉效果 | 10 种粒子效果正常 | ✅ |
| 碰撞检测 | 子弹、敌机、玩家碰撞正常 | ✅ |
| 无敌技能 | 正常免疫伤害 | ✅ |
| 爆炸动画 | 19 帧动画完整播放 | ✅ |
| 道具系统 | 5 种道具正常掉落 | ✅ |
| 护甲系统 | 3 层护甲正常生效 | ✅ |
| 胜利条件 | 第 10 关胜利正常触发 | ✅ |
BUG 修复统计
| BUG | 严重性 | 状态 |
|---|---|---|
| 无敌技能导致敌机不再生成 | 🔴 严重 | ✅ 已修复 |
| 敌机被摧毁无爆炸动画 | 🟠 中等 | ✅ 已修复 |
| 道具样式不符合要求 | 🟡 轻微 | ✅ 已修复 |
| 圆形背景位置不当 | 🟡 轻微 | ✅ 已修复 |
| 火焰位置不当 | 🟡 轻微 | ✅ 已修复 |
总计:修复 5 个 BUG
📈 项目最终成果
代码统计
| 指标 | 数量 |
|---|---|
| 总代码行数 | ~8000 行 |
| JavaScript 文件 | 15 个 |
| 敌机配置 | 50 种 |
| 关卡数量 | 10 关 |
| 背景粒子效果 | 10 种 |
| 道具类型 | 5 种 |
| 移动模式 | 4 种 |
| 修复 BUG 数 | 20+ 个 |
| 迭代次数 | 30+ 次 |
功能完成度
- ✅ 十关关卡系统(每关独特主题)
- ✅ 50 种敌机配置(每关 5 种)
- ✅ 4 种移动模式(直线、S 型、曲线、缓慢曲线)
- ✅ 10 种背景粒子效果
- ✅ 5 种道具系统(双发、加速、护甲、生命、炸弹)
- ✅ 无敌技能和炸弹技能
- ✅ 护甲系统(3 层护甲)
- ✅ 金币系统和商店
- ✅ 游戏胜利和失败判定
- ✅ 分享推荐机制
视觉效果
| 关卡 | 主题色 | 粒子效果 | 特色 |
|---|---|---|---|
| L1 | 青蓝色 | 10 条风线 | 稀疏纤细 |
| L2 | 岩石棕 | 晶体岩石 | 立体多面体 |
| L3 | 紫色 | 闪电分支 | 随机闪烁 |
| L4 | 森林绿 | 草丛摇曳 | 三叶曲线 |
| L5 | 海洋蓝 | 水泡上升 | 左右摇摆 |
| L6 | 火焰红 | 火焰喷射 | 脉动效果 |
| L7 | 冰雪蓝 | 雪花飘落 | 六角分支 |
| L8 | 灰色调 | 雾气粒子 | 模糊效果 |
| L9 | 代码绿 | 代码雨 | 矩阵效果 |
| L10 | 紫金色 | 粒子光环 | 同心圆 |
💡 开发心得
调试技巧
1. 复现问题
- 准确复现是解决问题的第一步
- 记录复现步骤
- 找到最小复现场景
2. 定位问题
- 使用 console.log 输出关键信息
- 检查数据流和状态变化
- 使用断点调试
3. 分析根源
- 不要只修复表面症状
- 找到问题的根本原因
- 考虑所有可能的影响因素
4. 验证修复
- 修复后重新测试
- 确保没有引入新 BUG
- 进行回归测试
AI 协作经验
有效沟通的要点:
-
清晰描述问题
❌ "游戏有问题" ✅ "使用无敌技能撞毁所有飞机后,不再生成新飞机" -
提供复现步骤
``` - 启动游戏
- 使用无敌技能
- 撞击摧毁所有飞机
-
观察不再有新飞机生成
``` -
说明期望行为
期望:摧毁飞机后应该继续生成新飞机 实际:不再生成新飞机 -
及时反馈验证
"修复有效,问题已解决" "还有类似问题,正常击毁后也不生成"
技术收获
1. 对象池模式
- 理解对象池的工作原理
- 掌握对象池的使用场景
- 注意对象池的生命周期管理
2. 动画系统
- 理解帧动画的播放机制
- 掌握动画与精灵的关系
- 注意动画状态的管理
3. 碰撞检测
- 矩形碰撞检测的实现
- 碰撞时机的把握
- 碰撞后的状态处理
4. 性能优化
- 粒子数量控制
- 渲染优化技巧
- 对象池预分配
📝 后续优化方向
短期优化(1-2 周)
-
性能优化
- 粒子效果 LOD(根据设备性能调整数量)
- 渲染批次合并
- 资源预加载优化 -
音效优化
- 添加背景音乐
- 不同关卡不同 BGM
- 音效音量平衡 -
UI 优化
- 更精美的开始界面
- 关卡选择界面
- 结算界面动画
中期优化(1-2 月)
-
游戏模式
- 无尽模式
- 时间挑战模式
- BOSS 战模式 -
社交功能
- 好友排行榜
- 成就系统
- 每日任务 -
商业化
- 皮肤系统
- 道具内购
- 广告激励视频
长期优化(3-6 月)
-
引擎升级
- 考虑迁移到 Cocos Creator
- 或使用 Phaser 引擎
- 提升开发效率 -
跨平台
- 抖音小游戏
- 支付宝小游戏
- Web 版本 -
IP 化
- 角色设计
- 故事背景
- 周边产品
🎓 三天开发总结
开发历程回顾
第一天:基础架构与敌机系统
- ✅ 敌机系统从 5 种扩展到 50 种
- ✅ 实现 4 种移动模式
- ✅ 构建十关关卡架构
- ✅ 设计难度曲线
第二天:视觉效果迭代
- ✅ 10 关背景粒子效果
- ✅ 26 次迭代优化
- ✅ 多轮需求沟通
- ✅ 视觉风格确定
第三天:BUG 修复与优化
- ✅ 修复无敌技能 BUG
- ✅ 修复爆炸动画 BUG
- ✅ 游戏平衡性调整
- ✅ 性能优化和测试
关键成功因素
-
明确的需求
- 清晰的关卡设计
- 明确的视觉风格
- 具体的功能要求 -
高效的沟通
- 快速响应反馈
- 小步迭代验证
- 及时确认效果 -
合理的架构
- 配置化设计
- 对象池优化
- 类继承体系 -
持续的优化
- 多轮视觉调整
- 性能持续优化
- BUG 及时修复
AI 辅助开发的价值
开发效率提升:
- 传统开发:2-3 周
- AI 辅助:3 天
- 效率提升:5-7 倍
代码质量提升:
- 自动添加注释
- 错误处理完善
- 边界检查到位
创意实现加速:
- 快速原型实现
- 多方案对比
- 快速迭代优化
学习成本降低:
- AI 解释代码
- 提供技术参考
- 最佳实践指导
未来展望
AI 辅助开发不是替代开发者,而是增强开发者的能力。
掌握 AI 协作技巧:
- 清晰表达需求
- 有效沟通反馈
- 快速迭代验证
- 技术决策判断
成为 AI 增强型开发者:
- 利用 AI 提升效率
- 保持技术判断力
- 专注创意设计
- 提升代码质量
三天开发完成
总开发时长:约 24 小时
总代码提交:12 次 commit
总迭代次数:30+ 次
总问题解决:40+ 个
文档记录:三天开发日志
项目状态:✅ 完成 1.0 版本,可上线测试
本文档记录了第三天的 BUG 修复和优化过程,是三天开发历程的收官之作。
🎉 感谢 AI 辅助开发,让创意快速变为现实!🚀
评论
发表评论