zdwalter commited on
Commit
66bddec
·
verified ·
1 Parent(s): 109e1ee

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +66 -191
index.html CHANGED
@@ -318,6 +318,12 @@
318
  </div>
319
  </div>
320
 
 
 
 
 
 
 
321
  <audio id="shootSound" src="https://assets.mixkit.co/sfx/preview/mixkit-laser-weapon-shot-1680.mp3" preload="auto"></audio>
322
  <audio id="explosionSound" src="https://assets.mixkit.co/sfx/preview/mixkit-explosion-impact-1684.mp3" preload="auto"></audio>
323
  <audio id="powerupSound" src="https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3" preload="auto"></audio>
@@ -344,17 +350,17 @@
344
  y: 0,
345
  width: 60,
346
  height: 60,
347
- velocity: 0, // 左右方向速度
348
- verticalVelocity: 0, // 上下方向速度
349
  rotation: 0,
350
  lastFireTime: 0,
351
- fireRate: 200, // 射击间隔(ms)
352
- bulletDamage: 1, // 子弹伤害
353
  hasShield: false,
354
  shieldDuration: 0,
355
  powerups: {
356
- rapidFire: 0, // 火力增强
357
- homingMissiles: 0, // 跟踪导弹
358
  }
359
  },
360
  stars: [],
@@ -364,16 +370,16 @@
364
  bullets: [],
365
  enemyBullets: [], // 敌人子弹
366
  debris: [],
367
- powerups: [], // 道具
368
- homingMissiles: [], // 跟踪导弹
369
- effects: [], // 特效
370
- particles: [], // 粒子效果
371
  lastStarTime: 0,
372
  lastObstacleTime: 0,
373
  lastCloudTime: 0,
374
  lastPowerupTime: 0,
375
- lastBossSpawnTime: 0, // 上次生成Boss时间
376
- lastEnemyShootTime: 0, // 敌人上次射击时间
377
  keys: {
378
  ArrowUp: false,
379
  ArrowDown: false,
@@ -386,10 +392,10 @@
386
  joystickAngle: 0,
387
  joystickDistance: 0,
388
  achievements: {
389
- firstBlood: false, // 第一次击杀
390
- combo5: false, // 连续5次击杀
391
- noDamage: false, // 无伤通关
392
- bossSlayer: false // 击败Boss
393
  },
394
  highScore: localStorage.getItem('highScore') || 0
395
  };
@@ -400,9 +406,9 @@
400
  id: 'rapidFire',
401
  icon: 'fas fa-bolt',
402
  color: 'red',
403
- duration: 10000, // 10秒
404
  effect: (game) => {
405
- game.plane.fireRate = 100; // 更快射击
406
  game.plane.powerups.rapidFire = Date.now() + POWERUP_TYPES.RAPID_FIRE.duration;
407
  createEffect('火力增强!', 'red', 1500);
408
  playSound('powerupSound');
@@ -412,7 +418,7 @@
412
  id: 'homingMissiles',
413
  icon: 'fas fa-rocket',
414
  color: 'purple',
415
- duration: 10000, // 10秒
416
  effect: (game) => {
417
  game.plane.powerups.homingMissiles = Date.now() + POWERUP_TYPES.HOMING_MISSILE.duration;
418
  createEffect('跟踪导弹已激活!', 'purple', 1500);
@@ -423,7 +429,7 @@
423
  id: 'shield',
424
  icon: 'fas fa-shield-alt',
425
  color: 'blue',
426
- duration: 8000, // 8秒
427
  effect: (game) => {
428
  game.plane.hasShield = true;
429
  game.plane.shieldDuration = Date.now() + POWERUP_TYPES.SHIELD.duration;
@@ -436,7 +442,7 @@
436
  icon: 'fas fa-heart',
437
  color: 'green',
438
  effect: (game) => {
439
- game.lives = Math.min(game.lives + 1, 5); // 最多5条命
440
  updateUI();
441
  createEffect('生命值恢复!', 'green', 1500);
442
  playSound('powerupSound');
@@ -446,7 +452,7 @@
446
  id: 'timeSlow',
447
  icon: 'fas fa-clock',
448
  color: 'cyan',
449
- duration: 8000, // 8秒
450
  effect: (game) => {
451
  game.timeSlow = Date.now() + POWERUP_TYPES.TIME_SLOW.duration;
452
  createEffect('时间减速!', 'cyan', 1500);
@@ -458,7 +464,6 @@
458
  icon: 'fas fa-bomb',
459
  color: 'orange',
460
  effect: (game) => {
461
- // 清除所有障碍物
462
  game.obstacles.forEach(obstacle => {
463
  createExplosion(obstacle.x, obstacle.y);
464
  createDebris(obstacle, 8);
@@ -472,7 +477,7 @@
472
  id: 'doubleScore',
473
  icon: 'fas fa-star',
474
  color: 'pink',
475
- duration: 10000, // 10秒
476
  effect: (game) => {
477
  game.doubleScore = Date.now() + POWERUP_TYPES.DOUBLE_SCORE.duration;
478
  createEffect('双倍分数!', 'pink', 1500);
@@ -756,7 +761,7 @@
756
  });
757
  }
758
 
759
- // 创建敌人子弹
760
  function createEnemyBullet(x, y) {
761
  const size = 8;
762
  const speed = 5 + gameState.speed / 50;
@@ -771,7 +776,7 @@
771
  size,
772
  speed,
773
  angle,
774
- damage: 1
775
  });
776
 
777
  playSound('enemyShootSound');
@@ -786,9 +791,8 @@
786
  const speed = Math.random() * 2 + 2 + gameState.speed / 50;
787
  const type = Math.random() > 0.5 ? 'rectangle' : 'circle';
788
  const health = type === 'rectangle' ? (width > 80 ? 3 : 2) : 1;
789
- const canShoot = Math.random() > 0.7; // 30%的敌人会射击
790
 
791
- // 碰撞体积比实际显示大20%
792
  const collisionWidth = width * 1.2;
793
  const collisionHeight = height * 1.2;
794
 
@@ -806,7 +810,7 @@
806
  isLarge: width > 80,
807
  canShoot,
808
  lastShootTime: 0,
809
- shootCooldown: Math.random() * 2000 + 1000 // 射击冷却时间1-3秒
810
  });
811
  }
812
 
@@ -839,7 +843,7 @@
839
  isBoss: true,
840
  canShoot: true,
841
  lastShootTime: 0,
842
- shootCooldown: 500 // Boss射击冷却时间0.5秒
843
  });
844
 
845
  createEffect('BOSS出现!', 'red', 2000);
@@ -852,7 +856,6 @@
852
  const y = Math.random() * (canvas.height - size * 2) + size;
853
  const speed = Math.random() * 2 + 1;
854
 
855
- // 随机选择一种道具类型
856
  const powerupKeys = Object.keys(POWERUP_TYPES);
857
  const randomPowerup = POWERUP_TYPES[powerupKeys[Math.floor(Math.random() * powerupKeys.length)]];
858
 
@@ -867,9 +870,8 @@
867
 
868
  // 创建跟踪导弹
869
  function createHomingMissile() {
870
- if (gameState.obstacles.length === 0) return; // 没有目标时不开火
871
 
872
- // 找到最近的障碍物作为目标
873
  let closestObstacle = null;
874
  let minDistance = Infinity;
875
 
@@ -919,12 +921,12 @@
919
 
920
  // 创建子弹
921
  function createBullet() {
922
- if (gameState.ammo <= 0) return false; // 没有弹药了
923
 
924
- const size = gameState.plane.powerups.rapidFire > Date.now() ? 10 : 8; // 火力增强时子弹更大
925
- const damage = gameState.plane.powerups.rapidFire > Date.now() ? 2 : 1; // 火力增强时伤害更高
926
- const speed = gameState.plane.powerups.rapidFire > Date.now() ? 18 : 15; // 火力增强时速度更快
927
- const x = gameState.plane.x + 30; // 从飞机前端发射
928
  const y = gameState.plane.y;
929
 
930
  gameState.bullets.push({
@@ -935,13 +937,11 @@
935
  damage
936
  });
937
 
938
- // 如果有跟踪导弹能力且冷却结束
939
  if (gameState.plane.powerups.homingMissiles > Date.now() &&
940
- Math.random() > 0.7) { // 70%概率发射跟踪导弹
941
  requestAnimationFrame(createHomingMissile);
942
  }
943
 
944
- // 如果弹药不是无限的,减少弹药
945
  if (gameState.ammo !== Infinity) {
946
  gameState.ammo--;
947
  }
@@ -982,14 +982,12 @@
982
  gameOverScreen.classList.remove('hidden');
983
  finalScore.textContent = gameState.score;
984
 
985
- // 更新最高分
986
  if (gameState.score > gameState.highScore) {
987
  gameState.highScore = gameState.score;
988
  localStorage.setItem('highScore', gameState.highScore);
989
  achievementsDisplay.innerHTML += `<div class="text-yellow-400 mb-2">🎉 新纪录!</div>`;
990
  }
991
 
992
- // 显示成就
993
  if (!gameState.achievements.firstBlood) {
994
  achievementsDisplay.innerHTML += `<div class="text-green-400 mb-2">🏆 首次击杀!</div>`;
995
  }
@@ -1003,7 +1001,6 @@
1003
  achievementsDisplay.innerHTML += `<div class="text-red-400 mb-2">👹 Boss杀手!</div>`;
1004
  }
1005
 
1006
- // 隐藏触摸控制按钮
1007
  leftBtn.classList.add('hidden');
1008
  rightBtn.classList.add('hidden');
1009
  upBtn.classList.add('hidden');
@@ -1018,43 +1015,34 @@
1018
  function gameLoop(timestamp) {
1019
  if (!gameState.started || gameState.gameOver) return;
1020
 
1021
- // 清除画布
1022
  ctx.clearRect(0, 0, canvas.width, canvas.height);
1023
 
1024
- // 更新游戏状态
1025
  updateGame(timestamp);
1026
 
1027
- // 绘制游戏元素
1028
  drawGame();
1029
 
1030
- // 继续循环
1031
  requestAnimationFrame(gameLoop);
1032
  }
1033
 
1034
  // 更新游戏状态
1035
  function updateGame(timestamp) {
1036
- // 随着分数增加难度
1037
  gameState.difficulty = 1 + Math.min(gameState.score / 1000, 3);
1038
 
1039
- // 检查Boss生成条件
1040
  if (!gameState.bossActive &&
1041
  gameState.score > 0 &&
1042
  gameState.score % 5000 === 0 &&
1043
- timestamp - gameState.lastBossSpawnTime > 30000) { // 每5000分且30秒内未生成Boss
1044
  createBoss();
1045
  }
1046
 
1047
- // 时间减速因子
1048
  const timeSlowFactor = gameState.timeSlow > Date.now() ? 0.5 : 1;
1049
 
1050
- // 处理开火
1051
  if ((gameState.keys.Space || gameState.isFiring) &&
1052
- timestamp - gameState.plane.lastFireTime > gameState.plane.fireRate) { // 射击冷却
1053
  createBullet();
1054
  gameState.plane.lastFireTime = timestamp;
1055
  }
1056
 
1057
- // 敌人射击
1058
  if (timestamp - gameState.lastEnemyShootTime > 1000 / gameState.difficulty) {
1059
  gameState.obstacles.forEach(obstacle => {
1060
  if (obstacle.canShoot && timestamp - obstacle.lastShootTime > obstacle.shootCooldown) {
@@ -1065,7 +1053,6 @@
1065
  gameState.lastEnemyShootTime = timestamp;
1066
  }
1067
 
1068
- // 更新敌人子弹
1069
  gameState.enemyBullets.forEach(bullet => {
1070
  bullet.x += Math.cos(bullet.angle) * bullet.speed * timeSlowFactor;
1071
  bullet.y += Math.sin(bullet.angle) * bullet.speed * timeSlowFactor;
@@ -1075,10 +1062,9 @@
1075
  bullet.y > 0 && bullet.y < canvas.height
1076
  );
1077
 
1078
- // 检查道具是否过期
1079
  if (gameState.plane.powerups.rapidFire > 0 && gameState.plane.powerups.rapidFire < Date.now()) {
1080
  gameState.plane.powerups.rapidFire = 0;
1081
- gameState.plane.fireRate = 200; // 恢复默认射击速度
1082
  createEffect('火力增强结束', 'red', 1500);
1083
  }
1084
 
@@ -1102,7 +1088,6 @@
1102
  createEffect('双倍分数结束', 'pink', 1500);
1103
  }
1104
 
1105
- // 更新飞机速度
1106
  if (gameState.keys.ArrowUp || gameState.keys.ArrowDown) {
1107
  gameState.speed = Math.max(50,
1108
  Math.min(200,
@@ -1111,7 +1096,6 @@
1111
  );
1112
  }
1113
 
1114
- // 更新水平方向移动
1115
  if (gameState.keys.ArrowLeft || gameState.joystickAngle < -Math.PI/4) {
1116
  gameState.plane.rotation = Math.max(gameState.plane.rotation - 2, -20);
1117
  gameState.plane.velocity = Math.max(gameState.plane.velocity - 0.5, -5);
@@ -1119,57 +1103,47 @@
1119
  gameState.plane.rotation = Math.min(gameState.plane.rotation + 2, 20);
1120
  gameState.plane.velocity = Math.min(gameState.plane.velocity + 0.5, 5);
1121
  } else {
1122
- // 如果没有按左右键,飞机逐渐回正
1123
  gameState.plane.rotation *= 0.95;
1124
  gameState.plane.velocity *= 0.95;
1125
  if (Math.abs(gameState.plane.rotation) < 0.5) gameState.plane.rotation = 0;
1126
  if (Math.abs(gameState.plane.velocity) < 0.5) gameState.plane.velocity = 0;
1127
  }
1128
 
1129
- // 更新垂直方向移动
1130
  if (gameState.keys.ArrowUp || (gameState.joystickActive && gameState.joystickAngle < -Math.PI/4 && gameState.joystickAngle > -3*Math.PI/4)) {
1131
  gameState.plane.verticalVelocity = Math.max(gameState.plane.verticalVelocity - 0.5, -5);
1132
  } else if (gameState.keys.ArrowDown || (gameState.joystickActive && gameState.joystickAngle > Math.PI/4 && gameState.joystickAngle < 3*Math.PI/4)) {
1133
  gameState.plane.verticalVelocity = Math.min(gameState.plane.verticalVelocity + 0.5, 5);
1134
  } else {
1135
- // 如果没有按上下键,垂直速度逐渐归零
1136
  gameState.plane.verticalVelocity *= 0.95;
1137
  if (Math.abs(gameState.plane.verticalVelocity) < 0.5) gameState.plane.verticalVelocity = 0;
1138
  }
1139
 
1140
- // 更新飞机位置
1141
  gameState.plane.x += gameState.plane.velocity;
1142
  gameState.plane.y += gameState.plane.verticalVelocity;
1143
 
1144
- // 限制飞机在屏幕内
1145
  gameState.plane.x = Math.max(gameState.plane.width / 2, Math.min(gameState.plane.x, canvas.width - gameState.plane.width / 2));
1146
  gameState.plane.y = Math.max(gameState.plane.height / 2, Math.min(gameState.plane.y, canvas.height - gameState.plane.height / 2));
1147
 
1148
- // 生成新星星
1149
  if (timestamp - gameState.lastStarTime > 2000 / gameState.difficulty) {
1150
  createStar();
1151
  gameState.lastStarTime = timestamp;
1152
  }
1153
 
1154
- // 生成新障碍物
1155
  if (timestamp - gameState.lastObstacleTime > 1500 / gameState.difficulty * timeSlowFactor) {
1156
  createObstacle();
1157
  gameState.lastObstacleTime = timestamp;
1158
  }
1159
 
1160
- // 生成新云朵
1161
  if (timestamp - gameState.lastCloudTime > 1000 * timeSlowFactor) {
1162
  createCloud();
1163
  gameState.lastCloudTime = timestamp;
1164
  }
1165
 
1166
- // 生成新道具 (每5-8秒)
1167
  if (timestamp - gameState.lastPowerupTime > (Math.random() * 3000 + 5000) / gameState.difficulty * timeSlowFactor) {
1168
  createPowerup();
1169
  gameState.lastPowerupTime = timestamp;
1170
  }
1171
 
1172
- // 更新粒子
1173
  gameState.particles.forEach(particle => {
1174
  particle.x += particle.speedX;
1175
  particle.y += particle.speedY;
@@ -1177,12 +1151,10 @@
1177
  });
1178
  gameState.particles = gameState.particles.filter(p => p.life > 0);
1179
 
1180
- // 更新特效
1181
  gameState.effects = gameState.effects.filter(effect =>
1182
  Date.now() - effect.startTime < effect.duration
1183
  );
1184
 
1185
- // 更新碎片
1186
  gameState.debris.forEach(debris => {
1187
  debris.x += debris.speedX;
1188
  debris.y += debris.speedY;
@@ -1191,19 +1163,15 @@
1191
  });
1192
  gameState.debris = gameState.debris.filter(debris => debris.opacity > 0);
1193
 
1194
- // 更新跟踪导弹
1195
  gameState.homingMissiles.forEach(missile => {
1196
  if (!missile.target || missile.target.hit) {
1197
- // 如果没有目标或目标已被击中,则直线飞行
1198
  missile.x += Math.cos(missile.angle) * missile.speed;
1199
  missile.y += Math.sin(missile.angle) * missile.speed;
1200
  } else {
1201
- // 计算新的角度以跟踪目标
1202
  const dx = missile.target.x - missile.x;
1203
  const dy = missile.target.y - missile.y;
1204
  const targetAngle = Math.atan2(dy, dx);
1205
 
1206
- // 平滑转向
1207
  let angleDiff = targetAngle - missile.angle;
1208
  if (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
1209
  if (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
@@ -1212,7 +1180,6 @@
1212
  missile.x += Math.cos(missile.angle) * missile.speed;
1213
  missile.y += Math.sin(missile.angle) * missile.speed;
1214
 
1215
- // 检测碰撞
1216
  const missileRect = {
1217
  x: missile.x - missile.size / 2,
1218
  y: missile.y - missile.size / 2,
@@ -1229,7 +1196,7 @@
1229
 
1230
  if (checkCollision(missileRect, targetRect)) {
1231
  missile.hit = true;
1232
- missile.target.health -= 3; // 导弹伤害更高
1233
 
1234
  if (missile.target.health <= 0) {
1235
  missile.target.hit = true;
@@ -1237,7 +1204,6 @@
1237
  gameState.score += gameState.doubleScore > Date.now() ? scoreBonus * 2 : scoreBonus;
1238
  updateUI();
1239
 
1240
- // 如果是大型障碍物但不是Boss,分裂成小型障碍物
1241
  if (missile.target.isLarge && !missile.target.isBoss) {
1242
  for (let i = 0; i < 3; i++) {
1243
  gameState.obstacles.push({
@@ -1256,17 +1222,14 @@
1256
  }
1257
  }
1258
 
1259
- // 如果是Boss,标记Boss已击败
1260
  if (missile.target.isBoss) {
1261
  gameState.bossActive = false;
1262
  gameState.achievements.bossSlayer = true;
1263
  }
1264
 
1265
- // 创建碎片效果
1266
  createDebris(missile.target, missile.target.isBoss ? 30 : 12);
1267
  }
1268
 
1269
- // 创建爆炸效果
1270
  createExplosion(missile.x, missile.y);
1271
  }
1272
  }
@@ -1277,17 +1240,14 @@
1277
  !missile.hit
1278
  );
1279
 
1280
- // 更新云朵
1281
  gameState.clouds.forEach(cloud => {
1282
  cloud.x -= cloud.speed * timeSlowFactor;
1283
  });
1284
  gameState.clouds = gameState.clouds.filter(cloud => cloud.x + cloud.size > 0);
1285
 
1286
- // 更新道具
1287
  gameState.powerups.forEach(powerup => {
1288
  powerup.x -= powerup.speed * timeSlowFactor;
1289
 
1290
- // 检测与飞机的碰撞
1291
  const planeRect = {
1292
  x: gameState.plane.x - gameState.plane.width / 2,
1293
  y: gameState.plane.y - gameState.plane.height / 2,
@@ -1313,13 +1273,11 @@
1313
  powerup.x + powerup.size > 0 && !powerup.collected
1314
  );
1315
 
1316
- // 更新子弹
1317
  gameState.bullets.forEach(bullet => {
1318
  bullet.x += bullet.speed * timeSlowFactor;
1319
  });
1320
  gameState.bullets = gameState.bullets.filter(bullet => bullet.x < canvas.width);
1321
 
1322
- // 检查敌人子弹与飞机的碰撞
1323
  gameState.enemyBullets.forEach(bullet => {
1324
  const bulletRect = {
1325
  x: bullet.x - bullet.size / 2,
@@ -1346,19 +1304,16 @@
1346
  endGame();
1347
  }
1348
  } else {
1349
- // 保护罩被击中
1350
  createExplosion(bullet.x, bullet.y);
1351
  }
1352
  }
1353
  });
1354
  gameState.enemyBullets = gameState.enemyBullets.filter(bullet => !bullet.hit);
1355
 
1356
- // 更新星星
1357
  gameState.stars.forEach(star => {
1358
  star.x -= star.speed * timeSlowFactor;
1359
  star.rotation += star.rotationSpeed;
1360
 
1361
- // 检测碰撞
1362
  const planeRect = {
1363
  x: gameState.plane.x - gameState.plane.width / 2,
1364
  y: gameState.plane.y - gameState.plane.height / 2,
@@ -1383,12 +1338,10 @@
1383
  });
1384
  gameState.stars = gameState.stars.filter(star => star.x + star.size > 0 && !star.collected);
1385
 
1386
- // 更新障碍物
1387
- let comboCount = 0; // 连续击杀计数器
1388
  gameState.obstacles.forEach(obstacle => {
1389
  obstacle.x -= obstacle.speed * timeSlowFactor;
1390
 
1391
- // 检测与飞机的碰撞
1392
  const planeRect = {
1393
  x: gameState.plane.x - gameState.plane.width / 2,
1394
  y: gameState.plane.y - gameState.plane.height / 2,
@@ -1404,7 +1357,6 @@
1404
  };
1405
 
1406
  if (checkCollision(planeRect, obstacleRect) && !gameState.gameOver) {
1407
- // 如果有保护罩则不会受伤
1408
  if (!gameState.plane.hasShield || gameState.plane.shieldDuration < Date.now()) {
1409
  obstacle.hit = true;
1410
  gameState.lives--;
@@ -1415,14 +1367,12 @@
1415
  endGame();
1416
  }
1417
  } else {
1418
- // 保护罩被击中
1419
  obstacle.hit = true;
1420
  createExplosion(obstacle.x, obstacle.y);
1421
  createDebris(obstacle, 4);
1422
  }
1423
  }
1424
 
1425
- // 检测与子弹的碰撞
1426
  if (!obstacle.hit) {
1427
  const bulletHits = [];
1428
 
@@ -1453,7 +1403,6 @@
1453
  updateUI();
1454
  comboCount++;
1455
 
1456
- // 如果是大型障碍物但不是Boss,分裂成小型障碍物
1457
  if (obstacle.isLarge && !obstacle.isBoss) {
1458
  for (let i = 0; i < 3; i++) {
1459
  gameState.obstacles.push({
@@ -1472,16 +1421,13 @@
1472
  }
1473
  }
1474
 
1475
- // 如果是Boss,标记Boss已击败
1476
  if (obstacle.isBoss) {
1477
  gameState.bossActive = false;
1478
  gameState.achievements.bossSlayer = true;
1479
  }
1480
 
1481
- // 创建碎片效果
1482
  createDebris(obstacle, obstacle.isBoss ? 30 : 8);
1483
 
1484
- // 首次击杀成就
1485
  if (!gameState.achievements.firstBlood) {
1486
  gameState.achievements.firstBlood = true;
1487
  createEffect('首次击杀!', 'green', 2000);
@@ -1490,13 +1436,11 @@
1490
  }
1491
  });
1492
 
1493
- // 连续击杀成就
1494
  if (comboCount >= 5 && !gameState.achievements.combo5) {
1495
  gameState.achievements.combo5 = true;
1496
  createEffect('连续5次击杀!', 'blue', 2000);
1497
  }
1498
 
1499
- // 移除已经击中的子弹
1500
  for (let i = bulletHits.length - 1; i >= 0; i--) {
1501
  gameState.bullets.splice(bulletHits[i], 1);
1502
  }
@@ -1504,7 +1448,6 @@
1504
  });
1505
  gameState.obstacles = gameState.obstacles.filter(obstacle => obstacle.x + obstacle.width > 0 && !obstacle.hit);
1506
 
1507
- // 更新爆炸效果
1508
  gameState.explosions.forEach(explosion => {
1509
  explosion.size += 2;
1510
  explosion.alpha -= 0.02;
@@ -1514,14 +1457,12 @@
1514
 
1515
  // 绘制游戏元素
1516
  function drawGame() {
1517
- // 绘制背景渐变
1518
  const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
1519
  gradient.addColorStop(0, '#1e3c72');
1520
  gradient.addColorStop(1, '#2a5298');
1521
  ctx.fillStyle = gradient;
1522
  ctx.fillRect(0, 0, canvas.width, canvas.height);
1523
 
1524
- // 绘制云朵
1525
  gameState.clouds.forEach(cloud => {
1526
  cloud.parts.forEach(part => {
1527
  ctx.beginPath();
@@ -1537,7 +1478,6 @@
1537
  });
1538
  });
1539
 
1540
- // 绘制粒子
1541
  gameState.particles.forEach(particle => {
1542
  ctx.beginPath();
1543
  ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
@@ -1547,7 +1487,6 @@
1547
  ctx.globalAlpha = 1;
1548
  });
1549
 
1550
- // 绘制碎片
1551
  gameState.debris.forEach(debris => {
1552
  ctx.save();
1553
  ctx.translate(debris.x, debris.y);
@@ -1564,7 +1503,6 @@
1564
  ctx.restore();
1565
  });
1566
 
1567
- // 绘制敌人子弹
1568
  gameState.enemyBullets.forEach(bullet => {
1569
  const gradient = ctx.createRadialGradient(
1570
  bullet.x, bullet.y, 0,
@@ -1578,7 +1516,6 @@
1578
  ctx.fillStyle = gradient;
1579
  ctx.fill();
1580
 
1581
- // 子弹尾迹
1582
  ctx.beginPath();
1583
  ctx.moveTo(bullet.x - Math.cos(bullet.angle) * 10, bullet.y - Math.sin(bullet.angle) * 10);
1584
  ctx.lineTo(bullet.x, bullet.y);
@@ -1587,12 +1524,10 @@
1587
  ctx.stroke();
1588
  });
1589
 
1590
- // 绘制道具
1591
  gameState.powerups.forEach(powerup => {
1592
  ctx.save();
1593
  ctx.translate(powerup.x, powerup.y);
1594
 
1595
- // 绘制闪光效果
1596
  ctx.beginPath();
1597
  ctx.arc(0, 0, powerup.size / 2, 0, Math.PI * 2);
1598
  const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, powerup.size / 2);
@@ -1603,18 +1538,15 @@
1603
  ctx.fill();
1604
  ctx.globalAlpha = 1;
1605
 
1606
- // 绘制道具图标背景
1607
  ctx.fillStyle = powerup.type.color;
1608
  ctx.beginPath();
1609
  ctx.arc(0, 0, powerup.size / 2 - 3, 0, Math.PI * 2);
1610
  ctx.fill();
1611
 
1612
- // 绘制边框
1613
  ctx.strokeStyle = 'white';
1614
  ctx.lineWidth = 2;
1615
  ctx.stroke();
1616
 
1617
- // 绘制道具图标(使用FontAwesome图标Unicode)
1618
  ctx.fillStyle = 'white';
1619
  ctx.font = '20px FontAwesome';
1620
  ctx.textAlign = 'center';
@@ -1624,13 +1556,11 @@
1624
  ctx.restore();
1625
  });
1626
 
1627
- // 绘制跟踪导弹
1628
  gameState.homingMissiles.forEach(missile => {
1629
  ctx.save();
1630
  ctx.translate(missile.x, missile.y);
1631
  ctx.rotate(missile.angle);
1632
 
1633
- // 导弹主体
1634
  ctx.fillStyle = 'red';
1635
  ctx.beginPath();
1636
  ctx.moveTo(missile.size / 2, 0);
@@ -1639,7 +1569,6 @@
1639
  ctx.closePath();
1640
  ctx.fill();
1641
 
1642
- // 火焰效果
1643
  ctx.fillStyle = 'orange';
1644
  ctx.beginPath();
1645
  ctx.moveTo(-missile.size / 2, -missile.size / 4);
@@ -1651,7 +1580,6 @@
1651
  ctx.restore();
1652
  });
1653
 
1654
- // 绘制子弹
1655
  gameState.bullets.forEach(bullet => {
1656
  const gradient = ctx.createRadialGradient(
1657
  bullet.x, bullet.y, 0,
@@ -1665,7 +1593,6 @@
1665
  ctx.fillStyle = gradient;
1666
  ctx.fill();
1667
 
1668
- // 子弹尾迹
1669
  ctx.beginPath();
1670
  ctx.moveTo(bullet.x - bullet.speed, bullet.y);
1671
  ctx.lineTo(bullet.x, bullet.y);
@@ -1674,7 +1601,6 @@
1674
  ctx.stroke();
1675
  });
1676
 
1677
- // 绘制星星
1678
  gameState.stars.forEach(star => {
1679
  ctx.save();
1680
  ctx.translate(star.x, star.y);
@@ -1723,7 +1649,6 @@
1723
  ctx.translate(obstacle.x, obstacle.y);
1724
 
1725
  if (obstacle.type === 'rectangle') {
1726
- // 绘制健康条 (如果是矩形障碍物)
1727
  if (obstacle.health < obstacle.maxHealth) {
1728
  const healthBarWidth = obstacle.isBoss ? 100 : 20;
1729
  ctx.fillStyle = 'red';
@@ -1742,9 +1667,7 @@
1742
  );
1743
  }
1744
 
1745
- // 判断是否为敌人飞机(带有射击能力的障碍物)
1746
  if (obstacle.canShoot) {
1747
- // 使用渐变和阴影美化敌机主体
1748
  let bodyGradient = ctx.createLinearGradient(-obstacle.width/2, 0, obstacle.width/2, 0);
1749
  if (obstacle.isBoss) {
1750
  bodyGradient.addColorStop(0, '#ff4d4d');
@@ -1764,7 +1687,6 @@
1764
  ctx.fill();
1765
  ctx.shadowBlur = 0;
1766
 
1767
- // 绘制机翼
1768
  let wingGradient = ctx.createLinearGradient(-obstacle.width/4, 0, obstacle.width/4, 0);
1769
  wingGradient.addColorStop(0, obstacle.isBoss ? '#a83232' : '#444');
1770
  wingGradient.addColorStop(1, obstacle.isBoss ? '#600000' : '#222');
@@ -1785,16 +1707,6 @@
1785
  ctx.closePath();
1786
  ctx.fill();
1787
 
1788
- // 绘制驾驶舱(使用径向渐变)
1789
- let cockpitGradient = ctx.createRadialGradient(obstacle.width/4, 0, 0, obstacle.width/4, 0, obstacle.width/8);
1790
- cockpitGradient.addColorStop(0, '#80dfff');
1791
- cockpitGradient.addColorStop(1, '#3498db');
1792
- ctx.fillStyle = cockpitGradient;
1793
- ctx.beginPath();
1794
- ctx.arc(obstacle.width/4, 0, obstacle.width/8, 0, Math.PI * 2);
1795
- ctx.fill();
1796
-
1797
- // 绘制尾翼
1798
  ctx.beginPath();
1799
  ctx.moveTo(-obstacle.width/2, 0);
1800
  ctx.lineTo(-obstacle.width/3, -obstacle.height/4);
@@ -1812,7 +1724,14 @@
1812
  ctx.closePath();
1813
  ctx.fill();
1814
 
1815
- // 如果是Boss,添加特殊标记
 
 
 
 
 
 
 
1816
  if (obstacle.isBoss) {
1817
  ctx.fillStyle = 'gold';
1818
  ctx.font = 'bold 16px Arial';
@@ -1820,7 +1739,6 @@
1820
  ctx.fillText('BOSS', 0, 0);
1821
  }
1822
  } else {
1823
- // 绘制普通矩形障碍物
1824
  ctx.fillStyle = obstacle.isBoss ? '#8B0000' : (obstacle.isLarge ? '#333' : '#555');
1825
  ctx.fillRect(
1826
  -obstacle.width / 2,
@@ -1828,7 +1746,6 @@
1828
  obstacle.width,
1829
  obstacle.height
1830
  );
1831
- // 添加一些细节
1832
  ctx.fillStyle = obstacle.isBoss ? '#600000' : (obstacle.isLarge ? '#222' : '#444');
1833
  ctx.fillRect(
1834
  -obstacle.width / 2 + 5,
@@ -1837,7 +1754,6 @@
1837
  obstacle.height - 10
1838
  );
1839
 
1840
- // 添加裂缝效果 (对于受损的大型障碍物)
1841
  if ((obstacle.isLarge || obstacle.isBoss) && obstacle.health < obstacle.maxHealth) {
1842
  ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
1843
  ctx.lineWidth = 2;
@@ -1855,7 +1771,6 @@
1855
  }
1856
  }
1857
 
1858
- // 如果是Boss,添加特殊标记
1859
  if (obstacle.isBoss) {
1860
  ctx.fillStyle = 'gold';
1861
  ctx.font = 'bold 20px Arial';
@@ -1865,46 +1780,25 @@
1865
  }
1866
  }
1867
  } else {
1868
- // 圆形障碍物
1869
- ctx.beginPath();
1870
- ctx.arc(0, 0, obstacle.width / 2, 0, Math.PI * 2);
1871
- ctx.fillStyle = '#555';
1872
- ctx.fill();
1873
-
1874
- // 添加一些细节
1875
- ctx.beginPath();
1876
- ctx.arc(0, 0, obstacle.width / 2 - 5, 0, Math.PI * 2);
1877
- ctx.fillStyle = '#444';
1878
- ctx.fill();
1879
-
1880
- // 添加裂缝效果 (对于受损的大型障碍物)
1881
- if (obstacle.isLarge && obstacle.health < obstacle.maxHealth) {
1882
- ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
1883
- ctx.lineWidth = 2;
1884
- for (let i = 0; i < 3; i++) {
1885
- ctx.beginPath();
1886
- ctx.moveTo(
1887
- Math.cos(i * 2) * obstacle.width / 4,
1888
- Math.sin(i * 2) * obstacle.width / 4
1889
- );
1890
- ctx.lineTo(
1891
- Math.cos(i * 2 + 1) * obstacle.width / 3,
1892
- Math.sin(i * 2 + 1) * obstacle.width / 3
1893
- );
1894
- ctx.stroke();
1895
- }
1896
  }
1897
  }
1898
 
1899
  ctx.restore();
1900
  });
1901
 
1902
- // 绘制飞机
1903
  ctx.save();
1904
  ctx.translate(gameState.plane.x, gameState.plane.y);
1905
  ctx.rotate(gameState.plane.rotation * Math.PI / 180);
1906
 
1907
- // 绘制保护罩
1908
  if (gameState.plane.hasShield && gameState.plane.shieldDuration > Date.now()) {
1909
  ctx.beginPath();
1910
  ctx.arc(0, 0, 45, 0, Math.PI * 2);
@@ -1912,7 +1806,6 @@
1912
  ctx.lineWidth = 3;
1913
  ctx.stroke();
1914
 
1915
- // 保护罩光晕
1916
  const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 45);
1917
  gradient.addColorStop(0, 'rgba(0, 204, 255, 0.2)');
1918
  gradient.addColorStop(1, 'rgba(0, 204, 255, 0)');
@@ -1920,7 +1813,6 @@
1920
  ctx.fill();
1921
  }
1922
 
1923
- // 飞机主体
1924
  ctx.beginPath();
1925
  ctx.moveTo(30, 0);
1926
  ctx.lineTo(-20, -15);
@@ -1930,13 +1822,11 @@
1930
  ctx.fillStyle = '#e74c3c';
1931
  ctx.fill();
1932
 
1933
- // 飞机窗户
1934
  ctx.beginPath();
1935
  ctx.arc(10, 0, 5, 0, Math.PI * 2);
1936
  ctx.fillStyle = '#3498db';
1937
  ctx.fill();
1938
 
1939
- // 飞机机翼
1940
  ctx.beginPath();
1941
  ctx.moveTo(5, 0);
1942
  ctx.lineTo(-5, -20);
@@ -1955,7 +1845,6 @@
1955
  ctx.fillStyle = '#c0392b';
1956
  ctx.fill();
1957
 
1958
- // 飞机尾翼
1959
  ctx.beginPath();
1960
  ctx.moveTo(-20, 0);
1961
  ctx.lineTo(-25, -10);
@@ -1976,7 +1865,6 @@
1976
 
1977
  ctx.restore();
1978
 
1979
- // 绘制爆炸效果
1980
  gameState.explosions.forEach(explosion => {
1981
  ctx.save();
1982
  ctx.translate(explosion.x, explosion.y);
@@ -1997,13 +1885,12 @@
1997
  ctx.restore();
1998
  });
1999
 
2000
- // 绘制特效文字
2001
  gameState.effects.forEach(effect => {
2002
  const timePassed = Date.now() - effect.startTime;
2003
  const progress = timePassed / effect.duration;
2004
 
2005
  ctx.save();
2006
- ctx.translate(effect.x, effect.y - progress * 50); // 文字向上移动
2007
  ctx.globalAlpha = 1 - progress * 0.8;
2008
 
2009
  ctx.font = 'bold 20px Arial';
@@ -2015,7 +1902,6 @@
2015
  ctx.restore();
2016
  });
2017
 
2018
- // 绘制速度线
2019
  if (gameState.speed > 120) {
2020
  for (let i = 0; i < 10; i++) {
2021
  const x = Math.random() * canvas.width;
@@ -2053,7 +1939,7 @@
2053
  'fas fa-bomb': 'f1e2',
2054
  'fas fa-star': 'f005'
2055
  };
2056
- return icons[iconClass] || 'f128'; // 默认返回问号图标
2057
  }
2058
 
2059
  // 虚拟摇杆控制
@@ -2076,7 +1962,6 @@
2076
  handleX = touch.clientX - startX;
2077
  handleY = touch.clientY - startY;
2078
 
2079
- // 限制摇杆移动范围
2080
  const distance = Math.sqrt(handleX * handleX + handleY * handleY);
2081
  if (distance > maxDistance) {
2082
  handleX = (handleX / distance) * maxDistance;
@@ -2085,7 +1970,6 @@
2085
 
2086
  handle.style.transform = `translate(${handleX}px, ${handleY}px)`;
2087
 
2088
- // 计算角度和距离
2089
  gameState.joystickAngle = Math.atan2(handleY, handleX);
2090
  gameState.joystickDistance = distance / maxDistance;
2091
  gameState.joystickActive = true;
@@ -2099,7 +1983,6 @@
2099
  handleX = touch.clientX - startX;
2100
  handleY = touch.clientY - startY;
2101
 
2102
- // 限制摇杆移动范围
2103
  const distance = Math.sqrt(handleX * handleX + handleY * handleY);
2104
  if (distance > maxDistance) {
2105
  handleX = (handleX / distance) * maxDistance;
@@ -2108,7 +1991,6 @@
2108
 
2109
  handle.style.transform = `translate(${handleX}px, ${handleY}px)`;
2110
 
2111
- // 计算角度和距离
2112
  gameState.joystickAngle = Math.atan2(handleY, handleX);
2113
  gameState.joystickDistance = distance / maxDistance;
2114
  });
@@ -2121,17 +2003,15 @@
2121
  });
2122
  }
2123
 
2124
- // 事件监听
2125
  window.addEventListener('resize', resizeCanvas);
2126
 
2127
- // 键盘控制
2128
  document.addEventListener('keydown', (e) => {
2129
  if (gameState.keys.hasOwnProperty(e.key)) {
2130
  gameState.keys[e.key] = true;
2131
  e.preventDefault();
2132
  }
2133
 
2134
- if (e.key === ' ' || e.key === 'Spacebar') { // 空格键射击
2135
  gameState.keys.Space = true;
2136
  e.preventDefault();
2137
  }
@@ -2149,7 +2029,6 @@
2149
  }
2150
  });
2151
 
2152
- // 触摸控制按钮事件
2153
  leftBtn.addEventListener('touchstart', (e) => {
2154
  e.preventDefault();
2155
  gameState.keys.ArrowLeft = true;
@@ -2195,7 +2074,6 @@
2195
  gameState.isFiring = false;
2196
  });
2197
 
2198
- // 鼠标控制按钮事件(用于桌面浏览器测试)
2199
  leftBtn.addEventListener('mousedown', (e) => {
2200
  e.preventDefault();
2201
  gameState.keys.ArrowLeft = true;
@@ -2261,15 +2139,12 @@
2261
  gameState.isFiring = false;
2262
  });
2263
 
2264
- // 按钮事件
2265
  startButton.addEventListener('click', initGame);
2266
  restartButton.addEventListener('click', initGame);
2267
 
2268
- // 初始调整画布大小
2269
  resizeCanvas();
2270
  setupJoystick();
2271
 
2272
- // 显示最高分
2273
  highScoreDisplay.textContent = gameState.highScore;
2274
  </script>
2275
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">
 
318
  </div>
319
  </div>
320
 
321
+ <!-- 加载陨石贴图(请替换src为你自己喜欢的图片地址) -->
322
+ <script>
323
+ const meteorImage = new Image();
324
+ meteorImage.src = "https://via.placeholder.com/80x80.png?text=Meteor";
325
+ </script>
326
+
327
  <audio id="shootSound" src="https://assets.mixkit.co/sfx/preview/mixkit-laser-weapon-shot-1680.mp3" preload="auto"></audio>
328
  <audio id="explosionSound" src="https://assets.mixkit.co/sfx/preview/mixkit-explosion-impact-1684.mp3" preload="auto"></audio>
329
  <audio id="powerupSound" src="https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3" preload="auto"></audio>
 
350
  y: 0,
351
  width: 60,
352
  height: 60,
353
+ velocity: 0,
354
+ verticalVelocity: 0,
355
  rotation: 0,
356
  lastFireTime: 0,
357
+ fireRate: 200,
358
+ bulletDamage: 1,
359
  hasShield: false,
360
  shieldDuration: 0,
361
  powerups: {
362
+ rapidFire: 0,
363
+ homingMissiles: 0,
364
  }
365
  },
366
  stars: [],
 
370
  bullets: [],
371
  enemyBullets: [], // 敌人子弹
372
  debris: [],
373
+ powerups: [],
374
+ homingMissiles: [],
375
+ effects: [],
376
+ particles: [],
377
  lastStarTime: 0,
378
  lastObstacleTime: 0,
379
  lastCloudTime: 0,
380
  lastPowerupTime: 0,
381
+ lastBossSpawnTime: 0,
382
+ lastEnemyShootTime: 0,
383
  keys: {
384
  ArrowUp: false,
385
  ArrowDown: false,
 
392
  joystickAngle: 0,
393
  joystickDistance: 0,
394
  achievements: {
395
+ firstBlood: false,
396
+ combo5: false,
397
+ noDamage: false,
398
+ bossSlayer: false
399
  },
400
  highScore: localStorage.getItem('highScore') || 0
401
  };
 
406
  id: 'rapidFire',
407
  icon: 'fas fa-bolt',
408
  color: 'red',
409
+ duration: 10000,
410
  effect: (game) => {
411
+ game.plane.fireRate = 100;
412
  game.plane.powerups.rapidFire = Date.now() + POWERUP_TYPES.RAPID_FIRE.duration;
413
  createEffect('火力增强!', 'red', 1500);
414
  playSound('powerupSound');
 
418
  id: 'homingMissiles',
419
  icon: 'fas fa-rocket',
420
  color: 'purple',
421
+ duration: 10000,
422
  effect: (game) => {
423
  game.plane.powerups.homingMissiles = Date.now() + POWERUP_TYPES.HOMING_MISSILE.duration;
424
  createEffect('跟踪导弹已激活!', 'purple', 1500);
 
429
  id: 'shield',
430
  icon: 'fas fa-shield-alt',
431
  color: 'blue',
432
+ duration: 8000,
433
  effect: (game) => {
434
  game.plane.hasShield = true;
435
  game.plane.shieldDuration = Date.now() + POWERUP_TYPES.SHIELD.duration;
 
442
  icon: 'fas fa-heart',
443
  color: 'green',
444
  effect: (game) => {
445
+ game.lives = Math.min(game.lives + 1, 5);
446
  updateUI();
447
  createEffect('生命值恢复!', 'green', 1500);
448
  playSound('powerupSound');
 
452
  id: 'timeSlow',
453
  icon: 'fas fa-clock',
454
  color: 'cyan',
455
+ duration: 8000,
456
  effect: (game) => {
457
  game.timeSlow = Date.now() + POWERUP_TYPES.TIME_SLOW.duration;
458
  createEffect('时间减速!', 'cyan', 1500);
 
464
  icon: 'fas fa-bomb',
465
  color: 'orange',
466
  effect: (game) => {
 
467
  game.obstacles.forEach(obstacle => {
468
  createExplosion(obstacle.x, obstacle.y);
469
  createDebris(obstacle, 8);
 
477
  id: 'doubleScore',
478
  icon: 'fas fa-star',
479
  color: 'pink',
480
+ duration: 10000,
481
  effect: (game) => {
482
  game.doubleScore = Date.now() + POWERUP_TYPES.DOUBLE_SCORE.duration;
483
  createEffect('双倍分数!', 'pink', 1500);
 
761
  });
762
  }
763
 
764
+ // 创建敌人子弹(增强伤害,将 damage 调整为2)
765
  function createEnemyBullet(x, y) {
766
  const size = 8;
767
  const speed = 5 + gameState.speed / 50;
 
776
  size,
777
  speed,
778
  angle,
779
+ damage: 2
780
  });
781
 
782
  playSound('enemyShootSound');
 
791
  const speed = Math.random() * 2 + 2 + gameState.speed / 50;
792
  const type = Math.random() > 0.5 ? 'rectangle' : 'circle';
793
  const health = type === 'rectangle' ? (width > 80 ? 3 : 2) : 1;
794
+ const canShoot = Math.random() > 0.7;
795
 
 
796
  const collisionWidth = width * 1.2;
797
  const collisionHeight = height * 1.2;
798
 
 
810
  isLarge: width > 80,
811
  canShoot,
812
  lastShootTime: 0,
813
+ shootCooldown: Math.random() * 2000 + 1000
814
  });
815
  }
816
 
 
843
  isBoss: true,
844
  canShoot: true,
845
  lastShootTime: 0,
846
+ shootCooldown: 500
847
  });
848
 
849
  createEffect('BOSS出现!', 'red', 2000);
 
856
  const y = Math.random() * (canvas.height - size * 2) + size;
857
  const speed = Math.random() * 2 + 1;
858
 
 
859
  const powerupKeys = Object.keys(POWERUP_TYPES);
860
  const randomPowerup = POWERUP_TYPES[powerupKeys[Math.floor(Math.random() * powerupKeys.length)]];
861
 
 
870
 
871
  // 创建跟踪导弹
872
  function createHomingMissile() {
873
+ if (gameState.obstacles.length === 0) return;
874
 
 
875
  let closestObstacle = null;
876
  let minDistance = Infinity;
877
 
 
921
 
922
  // 创建子弹
923
  function createBullet() {
924
+ if (gameState.ammo <= 0) return false;
925
 
926
+ const size = gameState.plane.powerups.rapidFire > Date.now() ? 10 : 8;
927
+ const damage = gameState.plane.powerups.rapidFire > Date.now() ? 2 : 1;
928
+ const speed = gameState.plane.powerups.rapidFire > Date.now() ? 18 : 15;
929
+ const x = gameState.plane.x + 30;
930
  const y = gameState.plane.y;
931
 
932
  gameState.bullets.push({
 
937
  damage
938
  });
939
 
 
940
  if (gameState.plane.powerups.homingMissiles > Date.now() &&
941
+ Math.random() > 0.7) {
942
  requestAnimationFrame(createHomingMissile);
943
  }
944
 
 
945
  if (gameState.ammo !== Infinity) {
946
  gameState.ammo--;
947
  }
 
982
  gameOverScreen.classList.remove('hidden');
983
  finalScore.textContent = gameState.score;
984
 
 
985
  if (gameState.score > gameState.highScore) {
986
  gameState.highScore = gameState.score;
987
  localStorage.setItem('highScore', gameState.highScore);
988
  achievementsDisplay.innerHTML += `<div class="text-yellow-400 mb-2">🎉 新纪录!</div>`;
989
  }
990
 
 
991
  if (!gameState.achievements.firstBlood) {
992
  achievementsDisplay.innerHTML += `<div class="text-green-400 mb-2">🏆 首次击杀!</div>`;
993
  }
 
1001
  achievementsDisplay.innerHTML += `<div class="text-red-400 mb-2">👹 Boss杀手!</div>`;
1002
  }
1003
 
 
1004
  leftBtn.classList.add('hidden');
1005
  rightBtn.classList.add('hidden');
1006
  upBtn.classList.add('hidden');
 
1015
  function gameLoop(timestamp) {
1016
  if (!gameState.started || gameState.gameOver) return;
1017
 
 
1018
  ctx.clearRect(0, 0, canvas.width, canvas.height);
1019
 
 
1020
  updateGame(timestamp);
1021
 
 
1022
  drawGame();
1023
 
 
1024
  requestAnimationFrame(gameLoop);
1025
  }
1026
 
1027
  // 更新游戏状态
1028
  function updateGame(timestamp) {
 
1029
  gameState.difficulty = 1 + Math.min(gameState.score / 1000, 3);
1030
 
 
1031
  if (!gameState.bossActive &&
1032
  gameState.score > 0 &&
1033
  gameState.score % 5000 === 0 &&
1034
+ timestamp - gameState.lastBossSpawnTime > 30000) {
1035
  createBoss();
1036
  }
1037
 
 
1038
  const timeSlowFactor = gameState.timeSlow > Date.now() ? 0.5 : 1;
1039
 
 
1040
  if ((gameState.keys.Space || gameState.isFiring) &&
1041
+ timestamp - gameState.plane.lastFireTime > gameState.plane.fireRate) {
1042
  createBullet();
1043
  gameState.plane.lastFireTime = timestamp;
1044
  }
1045
 
 
1046
  if (timestamp - gameState.lastEnemyShootTime > 1000 / gameState.difficulty) {
1047
  gameState.obstacles.forEach(obstacle => {
1048
  if (obstacle.canShoot && timestamp - obstacle.lastShootTime > obstacle.shootCooldown) {
 
1053
  gameState.lastEnemyShootTime = timestamp;
1054
  }
1055
 
 
1056
  gameState.enemyBullets.forEach(bullet => {
1057
  bullet.x += Math.cos(bullet.angle) * bullet.speed * timeSlowFactor;
1058
  bullet.y += Math.sin(bullet.angle) * bullet.speed * timeSlowFactor;
 
1062
  bullet.y > 0 && bullet.y < canvas.height
1063
  );
1064
 
 
1065
  if (gameState.plane.powerups.rapidFire > 0 && gameState.plane.powerups.rapidFire < Date.now()) {
1066
  gameState.plane.powerups.rapidFire = 0;
1067
+ gameState.plane.fireRate = 200;
1068
  createEffect('火力增强结束', 'red', 1500);
1069
  }
1070
 
 
1088
  createEffect('双倍分数结束', 'pink', 1500);
1089
  }
1090
 
 
1091
  if (gameState.keys.ArrowUp || gameState.keys.ArrowDown) {
1092
  gameState.speed = Math.max(50,
1093
  Math.min(200,
 
1096
  );
1097
  }
1098
 
 
1099
  if (gameState.keys.ArrowLeft || gameState.joystickAngle < -Math.PI/4) {
1100
  gameState.plane.rotation = Math.max(gameState.plane.rotation - 2, -20);
1101
  gameState.plane.velocity = Math.max(gameState.plane.velocity - 0.5, -5);
 
1103
  gameState.plane.rotation = Math.min(gameState.plane.rotation + 2, 20);
1104
  gameState.plane.velocity = Math.min(gameState.plane.velocity + 0.5, 5);
1105
  } else {
 
1106
  gameState.plane.rotation *= 0.95;
1107
  gameState.plane.velocity *= 0.95;
1108
  if (Math.abs(gameState.plane.rotation) < 0.5) gameState.plane.rotation = 0;
1109
  if (Math.abs(gameState.plane.velocity) < 0.5) gameState.plane.velocity = 0;
1110
  }
1111
 
 
1112
  if (gameState.keys.ArrowUp || (gameState.joystickActive && gameState.joystickAngle < -Math.PI/4 && gameState.joystickAngle > -3*Math.PI/4)) {
1113
  gameState.plane.verticalVelocity = Math.max(gameState.plane.verticalVelocity - 0.5, -5);
1114
  } else if (gameState.keys.ArrowDown || (gameState.joystickActive && gameState.joystickAngle > Math.PI/4 && gameState.joystickAngle < 3*Math.PI/4)) {
1115
  gameState.plane.verticalVelocity = Math.min(gameState.plane.verticalVelocity + 0.5, 5);
1116
  } else {
 
1117
  gameState.plane.verticalVelocity *= 0.95;
1118
  if (Math.abs(gameState.plane.verticalVelocity) < 0.5) gameState.plane.verticalVelocity = 0;
1119
  }
1120
 
 
1121
  gameState.plane.x += gameState.plane.velocity;
1122
  gameState.plane.y += gameState.plane.verticalVelocity;
1123
 
 
1124
  gameState.plane.x = Math.max(gameState.plane.width / 2, Math.min(gameState.plane.x, canvas.width - gameState.plane.width / 2));
1125
  gameState.plane.y = Math.max(gameState.plane.height / 2, Math.min(gameState.plane.y, canvas.height - gameState.plane.height / 2));
1126
 
 
1127
  if (timestamp - gameState.lastStarTime > 2000 / gameState.difficulty) {
1128
  createStar();
1129
  gameState.lastStarTime = timestamp;
1130
  }
1131
 
 
1132
  if (timestamp - gameState.lastObstacleTime > 1500 / gameState.difficulty * timeSlowFactor) {
1133
  createObstacle();
1134
  gameState.lastObstacleTime = timestamp;
1135
  }
1136
 
 
1137
  if (timestamp - gameState.lastCloudTime > 1000 * timeSlowFactor) {
1138
  createCloud();
1139
  gameState.lastCloudTime = timestamp;
1140
  }
1141
 
 
1142
  if (timestamp - gameState.lastPowerupTime > (Math.random() * 3000 + 5000) / gameState.difficulty * timeSlowFactor) {
1143
  createPowerup();
1144
  gameState.lastPowerupTime = timestamp;
1145
  }
1146
 
 
1147
  gameState.particles.forEach(particle => {
1148
  particle.x += particle.speedX;
1149
  particle.y += particle.speedY;
 
1151
  });
1152
  gameState.particles = gameState.particles.filter(p => p.life > 0);
1153
 
 
1154
  gameState.effects = gameState.effects.filter(effect =>
1155
  Date.now() - effect.startTime < effect.duration
1156
  );
1157
 
 
1158
  gameState.debris.forEach(debris => {
1159
  debris.x += debris.speedX;
1160
  debris.y += debris.speedY;
 
1163
  });
1164
  gameState.debris = gameState.debris.filter(debris => debris.opacity > 0);
1165
 
 
1166
  gameState.homingMissiles.forEach(missile => {
1167
  if (!missile.target || missile.target.hit) {
 
1168
  missile.x += Math.cos(missile.angle) * missile.speed;
1169
  missile.y += Math.sin(missile.angle) * missile.speed;
1170
  } else {
 
1171
  const dx = missile.target.x - missile.x;
1172
  const dy = missile.target.y - missile.y;
1173
  const targetAngle = Math.atan2(dy, dx);
1174
 
 
1175
  let angleDiff = targetAngle - missile.angle;
1176
  if (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
1177
  if (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
 
1180
  missile.x += Math.cos(missile.angle) * missile.speed;
1181
  missile.y += Math.sin(missile.angle) * missile.speed;
1182
 
 
1183
  const missileRect = {
1184
  x: missile.x - missile.size / 2,
1185
  y: missile.y - missile.size / 2,
 
1196
 
1197
  if (checkCollision(missileRect, targetRect)) {
1198
  missile.hit = true;
1199
+ missile.target.health -= 3;
1200
 
1201
  if (missile.target.health <= 0) {
1202
  missile.target.hit = true;
 
1204
  gameState.score += gameState.doubleScore > Date.now() ? scoreBonus * 2 : scoreBonus;
1205
  updateUI();
1206
 
 
1207
  if (missile.target.isLarge && !missile.target.isBoss) {
1208
  for (let i = 0; i < 3; i++) {
1209
  gameState.obstacles.push({
 
1222
  }
1223
  }
1224
 
 
1225
  if (missile.target.isBoss) {
1226
  gameState.bossActive = false;
1227
  gameState.achievements.bossSlayer = true;
1228
  }
1229
 
 
1230
  createDebris(missile.target, missile.target.isBoss ? 30 : 12);
1231
  }
1232
 
 
1233
  createExplosion(missile.x, missile.y);
1234
  }
1235
  }
 
1240
  !missile.hit
1241
  );
1242
 
 
1243
  gameState.clouds.forEach(cloud => {
1244
  cloud.x -= cloud.speed * timeSlowFactor;
1245
  });
1246
  gameState.clouds = gameState.clouds.filter(cloud => cloud.x + cloud.size > 0);
1247
 
 
1248
  gameState.powerups.forEach(powerup => {
1249
  powerup.x -= powerup.speed * timeSlowFactor;
1250
 
 
1251
  const planeRect = {
1252
  x: gameState.plane.x - gameState.plane.width / 2,
1253
  y: gameState.plane.y - gameState.plane.height / 2,
 
1273
  powerup.x + powerup.size > 0 && !powerup.collected
1274
  );
1275
 
 
1276
  gameState.bullets.forEach(bullet => {
1277
  bullet.x += bullet.speed * timeSlowFactor;
1278
  });
1279
  gameState.bullets = gameState.bullets.filter(bullet => bullet.x < canvas.width);
1280
 
 
1281
  gameState.enemyBullets.forEach(bullet => {
1282
  const bulletRect = {
1283
  x: bullet.x - bullet.size / 2,
 
1304
  endGame();
1305
  }
1306
  } else {
 
1307
  createExplosion(bullet.x, bullet.y);
1308
  }
1309
  }
1310
  });
1311
  gameState.enemyBullets = gameState.enemyBullets.filter(bullet => !bullet.hit);
1312
 
 
1313
  gameState.stars.forEach(star => {
1314
  star.x -= star.speed * timeSlowFactor;
1315
  star.rotation += star.rotationSpeed;
1316
 
 
1317
  const planeRect = {
1318
  x: gameState.plane.x - gameState.plane.width / 2,
1319
  y: gameState.plane.y - gameState.plane.height / 2,
 
1338
  });
1339
  gameState.stars = gameState.stars.filter(star => star.x + star.size > 0 && !star.collected);
1340
 
1341
+ let comboCount = 0;
 
1342
  gameState.obstacles.forEach(obstacle => {
1343
  obstacle.x -= obstacle.speed * timeSlowFactor;
1344
 
 
1345
  const planeRect = {
1346
  x: gameState.plane.x - gameState.plane.width / 2,
1347
  y: gameState.plane.y - gameState.plane.height / 2,
 
1357
  };
1358
 
1359
  if (checkCollision(planeRect, obstacleRect) && !gameState.gameOver) {
 
1360
  if (!gameState.plane.hasShield || gameState.plane.shieldDuration < Date.now()) {
1361
  obstacle.hit = true;
1362
  gameState.lives--;
 
1367
  endGame();
1368
  }
1369
  } else {
 
1370
  obstacle.hit = true;
1371
  createExplosion(obstacle.x, obstacle.y);
1372
  createDebris(obstacle, 4);
1373
  }
1374
  }
1375
 
 
1376
  if (!obstacle.hit) {
1377
  const bulletHits = [];
1378
 
 
1403
  updateUI();
1404
  comboCount++;
1405
 
 
1406
  if (obstacle.isLarge && !obstacle.isBoss) {
1407
  for (let i = 0; i < 3; i++) {
1408
  gameState.obstacles.push({
 
1421
  }
1422
  }
1423
 
 
1424
  if (obstacle.isBoss) {
1425
  gameState.bossActive = false;
1426
  gameState.achievements.bossSlayer = true;
1427
  }
1428
 
 
1429
  createDebris(obstacle, obstacle.isBoss ? 30 : 8);
1430
 
 
1431
  if (!gameState.achievements.firstBlood) {
1432
  gameState.achievements.firstBlood = true;
1433
  createEffect('首次击杀!', 'green', 2000);
 
1436
  }
1437
  });
1438
 
 
1439
  if (comboCount >= 5 && !gameState.achievements.combo5) {
1440
  gameState.achievements.combo5 = true;
1441
  createEffect('连续5次击杀!', 'blue', 2000);
1442
  }
1443
 
 
1444
  for (let i = bulletHits.length - 1; i >= 0; i--) {
1445
  gameState.bullets.splice(bulletHits[i], 1);
1446
  }
 
1448
  });
1449
  gameState.obstacles = gameState.obstacles.filter(obstacle => obstacle.x + obstacle.width > 0 && !obstacle.hit);
1450
 
 
1451
  gameState.explosions.forEach(explosion => {
1452
  explosion.size += 2;
1453
  explosion.alpha -= 0.02;
 
1457
 
1458
  // 绘制游戏元素
1459
  function drawGame() {
 
1460
  const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
1461
  gradient.addColorStop(0, '#1e3c72');
1462
  gradient.addColorStop(1, '#2a5298');
1463
  ctx.fillStyle = gradient;
1464
  ctx.fillRect(0, 0, canvas.width, canvas.height);
1465
 
 
1466
  gameState.clouds.forEach(cloud => {
1467
  cloud.parts.forEach(part => {
1468
  ctx.beginPath();
 
1478
  });
1479
  });
1480
 
 
1481
  gameState.particles.forEach(particle => {
1482
  ctx.beginPath();
1483
  ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
 
1487
  ctx.globalAlpha = 1;
1488
  });
1489
 
 
1490
  gameState.debris.forEach(debris => {
1491
  ctx.save();
1492
  ctx.translate(debris.x, debris.y);
 
1503
  ctx.restore();
1504
  });
1505
 
 
1506
  gameState.enemyBullets.forEach(bullet => {
1507
  const gradient = ctx.createRadialGradient(
1508
  bullet.x, bullet.y, 0,
 
1516
  ctx.fillStyle = gradient;
1517
  ctx.fill();
1518
 
 
1519
  ctx.beginPath();
1520
  ctx.moveTo(bullet.x - Math.cos(bullet.angle) * 10, bullet.y - Math.sin(bullet.angle) * 10);
1521
  ctx.lineTo(bullet.x, bullet.y);
 
1524
  ctx.stroke();
1525
  });
1526
 
 
1527
  gameState.powerups.forEach(powerup => {
1528
  ctx.save();
1529
  ctx.translate(powerup.x, powerup.y);
1530
 
 
1531
  ctx.beginPath();
1532
  ctx.arc(0, 0, powerup.size / 2, 0, Math.PI * 2);
1533
  const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, powerup.size / 2);
 
1538
  ctx.fill();
1539
  ctx.globalAlpha = 1;
1540
 
 
1541
  ctx.fillStyle = powerup.type.color;
1542
  ctx.beginPath();
1543
  ctx.arc(0, 0, powerup.size / 2 - 3, 0, Math.PI * 2);
1544
  ctx.fill();
1545
 
 
1546
  ctx.strokeStyle = 'white';
1547
  ctx.lineWidth = 2;
1548
  ctx.stroke();
1549
 
 
1550
  ctx.fillStyle = 'white';
1551
  ctx.font = '20px FontAwesome';
1552
  ctx.textAlign = 'center';
 
1556
  ctx.restore();
1557
  });
1558
 
 
1559
  gameState.homingMissiles.forEach(missile => {
1560
  ctx.save();
1561
  ctx.translate(missile.x, missile.y);
1562
  ctx.rotate(missile.angle);
1563
 
 
1564
  ctx.fillStyle = 'red';
1565
  ctx.beginPath();
1566
  ctx.moveTo(missile.size / 2, 0);
 
1569
  ctx.closePath();
1570
  ctx.fill();
1571
 
 
1572
  ctx.fillStyle = 'orange';
1573
  ctx.beginPath();
1574
  ctx.moveTo(-missile.size / 2, -missile.size / 4);
 
1580
  ctx.restore();
1581
  });
1582
 
 
1583
  gameState.bullets.forEach(bullet => {
1584
  const gradient = ctx.createRadialGradient(
1585
  bullet.x, bullet.y, 0,
 
1593
  ctx.fillStyle = gradient;
1594
  ctx.fill();
1595
 
 
1596
  ctx.beginPath();
1597
  ctx.moveTo(bullet.x - bullet.speed, bullet.y);
1598
  ctx.lineTo(bullet.x, bullet.y);
 
1601
  ctx.stroke();
1602
  });
1603
 
 
1604
  gameState.stars.forEach(star => {
1605
  ctx.save();
1606
  ctx.translate(star.x, star.y);
 
1649
  ctx.translate(obstacle.x, obstacle.y);
1650
 
1651
  if (obstacle.type === 'rectangle') {
 
1652
  if (obstacle.health < obstacle.maxHealth) {
1653
  const healthBarWidth = obstacle.isBoss ? 100 : 20;
1654
  ctx.fillStyle = 'red';
 
1667
  );
1668
  }
1669
 
 
1670
  if (obstacle.canShoot) {
 
1671
  let bodyGradient = ctx.createLinearGradient(-obstacle.width/2, 0, obstacle.width/2, 0);
1672
  if (obstacle.isBoss) {
1673
  bodyGradient.addColorStop(0, '#ff4d4d');
 
1687
  ctx.fill();
1688
  ctx.shadowBlur = 0;
1689
 
 
1690
  let wingGradient = ctx.createLinearGradient(-obstacle.width/4, 0, obstacle.width/4, 0);
1691
  wingGradient.addColorStop(0, obstacle.isBoss ? '#a83232' : '#444');
1692
  wingGradient.addColorStop(1, obstacle.isBoss ? '#600000' : '#222');
 
1707
  ctx.closePath();
1708
  ctx.fill();
1709
 
 
 
 
 
 
 
 
 
 
 
1710
  ctx.beginPath();
1711
  ctx.moveTo(-obstacle.width/2, 0);
1712
  ctx.lineTo(-obstacle.width/3, -obstacle.height/4);
 
1724
  ctx.closePath();
1725
  ctx.fill();
1726
 
1727
+ let cockpitGradient = ctx.createRadialGradient(obstacle.width/4, 0, 0, obstacle.width/4, 0, obstacle.width/8);
1728
+ cockpitGradient.addColorStop(0, '#80dfff');
1729
+ cockpitGradient.addColorStop(1, '#3498db');
1730
+ ctx.fillStyle = cockpitGradient;
1731
+ ctx.beginPath();
1732
+ ctx.arc(obstacle.width/4, 0, obstacle.width/8, 0, Math.PI * 2);
1733
+ ctx.fill();
1734
+
1735
  if (obstacle.isBoss) {
1736
  ctx.fillStyle = 'gold';
1737
  ctx.font = 'bold 16px Arial';
 
1739
  ctx.fillText('BOSS', 0, 0);
1740
  }
1741
  } else {
 
1742
  ctx.fillStyle = obstacle.isBoss ? '#8B0000' : (obstacle.isLarge ? '#333' : '#555');
1743
  ctx.fillRect(
1744
  -obstacle.width / 2,
 
1746
  obstacle.width,
1747
  obstacle.height
1748
  );
 
1749
  ctx.fillStyle = obstacle.isBoss ? '#600000' : (obstacle.isLarge ? '#222' : '#444');
1750
  ctx.fillRect(
1751
  -obstacle.width / 2 + 5,
 
1754
  obstacle.height - 10
1755
  );
1756
 
 
1757
  if ((obstacle.isLarge || obstacle.isBoss) && obstacle.health < obstacle.maxHealth) {
1758
  ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
1759
  ctx.lineWidth = 2;
 
1771
  }
1772
  }
1773
 
 
1774
  if (obstacle.isBoss) {
1775
  ctx.fillStyle = 'gold';
1776
  ctx.font = 'bold 20px Arial';
 
1780
  }
1781
  }
1782
  } else {
1783
+ // 如果障碍物类型为"circle",我们将其视为陨石并绘制贴图
1784
+ if (meteorImage.complete) {
1785
+ ctx.drawImage(meteorImage, -obstacle.width / 2, -obstacle.height / 2, obstacle.width, obstacle.height);
1786
+ } else {
1787
+ // 贴图未加载完成时绘制占位圆
1788
+ ctx.beginPath();
1789
+ ctx.arc(0, 0, obstacle.width / 2, 0, Math.PI * 2);
1790
+ ctx.fillStyle = '#555';
1791
+ ctx.fill();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1792
  }
1793
  }
1794
 
1795
  ctx.restore();
1796
  });
1797
 
 
1798
  ctx.save();
1799
  ctx.translate(gameState.plane.x, gameState.plane.y);
1800
  ctx.rotate(gameState.plane.rotation * Math.PI / 180);
1801
 
 
1802
  if (gameState.plane.hasShield && gameState.plane.shieldDuration > Date.now()) {
1803
  ctx.beginPath();
1804
  ctx.arc(0, 0, 45, 0, Math.PI * 2);
 
1806
  ctx.lineWidth = 3;
1807
  ctx.stroke();
1808
 
 
1809
  const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 45);
1810
  gradient.addColorStop(0, 'rgba(0, 204, 255, 0.2)');
1811
  gradient.addColorStop(1, 'rgba(0, 204, 255, 0)');
 
1813
  ctx.fill();
1814
  }
1815
 
 
1816
  ctx.beginPath();
1817
  ctx.moveTo(30, 0);
1818
  ctx.lineTo(-20, -15);
 
1822
  ctx.fillStyle = '#e74c3c';
1823
  ctx.fill();
1824
 
 
1825
  ctx.beginPath();
1826
  ctx.arc(10, 0, 5, 0, Math.PI * 2);
1827
  ctx.fillStyle = '#3498db';
1828
  ctx.fill();
1829
 
 
1830
  ctx.beginPath();
1831
  ctx.moveTo(5, 0);
1832
  ctx.lineTo(-5, -20);
 
1845
  ctx.fillStyle = '#c0392b';
1846
  ctx.fill();
1847
 
 
1848
  ctx.beginPath();
1849
  ctx.moveTo(-20, 0);
1850
  ctx.lineTo(-25, -10);
 
1865
 
1866
  ctx.restore();
1867
 
 
1868
  gameState.explosions.forEach(explosion => {
1869
  ctx.save();
1870
  ctx.translate(explosion.x, explosion.y);
 
1885
  ctx.restore();
1886
  });
1887
 
 
1888
  gameState.effects.forEach(effect => {
1889
  const timePassed = Date.now() - effect.startTime;
1890
  const progress = timePassed / effect.duration;
1891
 
1892
  ctx.save();
1893
+ ctx.translate(effect.x, effect.y - progress * 50);
1894
  ctx.globalAlpha = 1 - progress * 0.8;
1895
 
1896
  ctx.font = 'bold 20px Arial';
 
1902
  ctx.restore();
1903
  });
1904
 
 
1905
  if (gameState.speed > 120) {
1906
  for (let i = 0; i < 10; i++) {
1907
  const x = Math.random() * canvas.width;
 
1939
  'fas fa-bomb': 'f1e2',
1940
  'fas fa-star': 'f005'
1941
  };
1942
+ return icons[iconClass] || 'f128';
1943
  }
1944
 
1945
  // 虚拟摇杆控制
 
1962
  handleX = touch.clientX - startX;
1963
  handleY = touch.clientY - startY;
1964
 
 
1965
  const distance = Math.sqrt(handleX * handleX + handleY * handleY);
1966
  if (distance > maxDistance) {
1967
  handleX = (handleX / distance) * maxDistance;
 
1970
 
1971
  handle.style.transform = `translate(${handleX}px, ${handleY}px)`;
1972
 
 
1973
  gameState.joystickAngle = Math.atan2(handleY, handleX);
1974
  gameState.joystickDistance = distance / maxDistance;
1975
  gameState.joystickActive = true;
 
1983
  handleX = touch.clientX - startX;
1984
  handleY = touch.clientY - startY;
1985
 
 
1986
  const distance = Math.sqrt(handleX * handleX + handleY * handleY);
1987
  if (distance > maxDistance) {
1988
  handleX = (handleX / distance) * maxDistance;
 
1991
 
1992
  handle.style.transform = `translate(${handleX}px, ${handleY}px)`;
1993
 
 
1994
  gameState.joystickAngle = Math.atan2(handleY, handleX);
1995
  gameState.joystickDistance = distance / maxDistance;
1996
  });
 
2003
  });
2004
  }
2005
 
 
2006
  window.addEventListener('resize', resizeCanvas);
2007
 
 
2008
  document.addEventListener('keydown', (e) => {
2009
  if (gameState.keys.hasOwnProperty(e.key)) {
2010
  gameState.keys[e.key] = true;
2011
  e.preventDefault();
2012
  }
2013
 
2014
+ if (e.key === ' ' || e.key === 'Spacebar') {
2015
  gameState.keys.Space = true;
2016
  e.preventDefault();
2017
  }
 
2029
  }
2030
  });
2031
 
 
2032
  leftBtn.addEventListener('touchstart', (e) => {
2033
  e.preventDefault();
2034
  gameState.keys.ArrowLeft = true;
 
2074
  gameState.isFiring = false;
2075
  });
2076
 
 
2077
  leftBtn.addEventListener('mousedown', (e) => {
2078
  e.preventDefault();
2079
  gameState.keys.ArrowLeft = true;
 
2139
  gameState.isFiring = false;
2140
  });
2141
 
 
2142
  startButton.addEventListener('click', initGame);
2143
  restartButton.addEventListener('click', initGame);
2144
 
 
2145
  resizeCanvas();
2146
  setupJoystick();
2147
 
 
2148
  highScoreDisplay.textContent = gameState.highScore;
2149
  </script>
2150
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">