Spaces:
Running
Running
Add 1 files
Browse files- index.html +499 -34
index.html
CHANGED
@@ -112,6 +112,39 @@
|
|
112 |
background-color: #777;
|
113 |
border-radius: 2px;
|
114 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
</style>
|
116 |
</head>
|
117 |
<body class="bg-gray-900 text-white flex flex-col items-center justify-center h-screen">
|
@@ -122,27 +155,26 @@
|
|
122 |
<div id="startScreen" class="game-overlay flex flex-col items-center justify-center bg-black bg-opacity-70">
|
123 |
<h1 class="text-5xl font-bold mb-6 text-yellow-300">SKY ADVENTURE</h1>
|
124 |
<div class="plane text-6xl mb-8">✈️</div>
|
125 |
-
<p class="text-xl mb-8 text-center max-w-md px-4">控制飞机躲避障碍物<br>收集星星获得高分!<br>按射击按钮消灭障碍物!<br
|
126 |
<button id="startButton" class="bg-yellow-500 hover:bg-yellow-600 text-black font-bold py-3 px-8 rounded-full text-xl transition-all duration-300 transform hover:scale-105 pointer-events-auto">
|
127 |
开始游戏
|
128 |
</button>
|
129 |
-
<div class="mt-8
|
130 |
<div class="flex items-center">
|
131 |
-
<
|
132 |
-
<span
|
133 |
</div>
|
134 |
<div class="flex items-center">
|
135 |
-
<
|
136 |
-
<span
|
137 |
</div>
|
138 |
<div class="flex items-center">
|
139 |
-
<
|
140 |
-
<
|
141 |
-
<span class="ml-2">转向</span>
|
142 |
</div>
|
143 |
<div class="flex items-center">
|
144 |
-
<
|
145 |
-
<span
|
146 |
</div>
|
147 |
</div>
|
148 |
</div>
|
@@ -175,6 +207,11 @@
|
|
175 |
</div>
|
176 |
</div>
|
177 |
|
|
|
|
|
|
|
|
|
|
|
178 |
<!-- 触摸控制按钮 -->
|
179 |
<div id="leftBtn" class="control-btn hidden">
|
180 |
<i class="fas fa-arrow-left"></i>
|
@@ -223,7 +260,15 @@
|
|
223 |
velocity: 0, // 左右方向速度
|
224 |
verticalVelocity: 0, // 上下方向速度
|
225 |
rotation: 0,
|
226 |
-
lastFireTime: 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
},
|
228 |
stars: [],
|
229 |
obstacles: [],
|
@@ -231,9 +276,13 @@
|
|
231 |
explosions: [],
|
232 |
bullets: [],
|
233 |
debris: [],
|
|
|
|
|
|
|
234 |
lastStarTime: 0,
|
235 |
lastObstacleTime: 0,
|
236 |
lastCloudTime: 0,
|
|
|
237 |
keys: {
|
238 |
ArrowUp: false,
|
239 |
ArrowDown: false,
|
@@ -244,6 +293,52 @@
|
|
244 |
isMobile: false
|
245 |
};
|
246 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
247 |
// 获取DOM元素
|
248 |
const canvas = document.getElementById('gameCanvas');
|
249 |
const ctx = canvas.getContext('2d');
|
@@ -257,6 +352,7 @@
|
|
257 |
const ammoDisplay = document.getElementById('ammoDisplay');
|
258 |
const speedDisplay = document.getElementById('speedDisplay');
|
259 |
const finalScore = document.getElementById('finalScore');
|
|
|
260 |
const leftBtn = document.getElementById('leftBtn');
|
261 |
const rightBtn = document.getElementById('rightBtn');
|
262 |
const upBtn = document.getElementById('upBtn');
|
@@ -278,6 +374,57 @@
|
|
278 |
}
|
279 |
}
|
280 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
281 |
// 初始化游戏
|
282 |
function initGame() {
|
283 |
gameState.isMobile = detectMobile();
|
@@ -297,7 +444,15 @@
|
|
297 |
velocity: 0,
|
298 |
verticalVelocity: 0,
|
299 |
rotation: 0,
|
300 |
-
lastFireTime: 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
301 |
};
|
302 |
gameState.stars = [];
|
303 |
gameState.obstacles = [];
|
@@ -305,9 +460,13 @@
|
|
305 |
gameState.explosions = [];
|
306 |
gameState.bullets = [];
|
307 |
gameState.debris = [];
|
|
|
|
|
|
|
308 |
gameState.lastStarTime = 0;
|
309 |
gameState.lastObstacleTime = 0;
|
310 |
gameState.lastCloudTime = 0;
|
|
|
311 |
|
312 |
startScreen.classList.add('hidden');
|
313 |
gameOverScreen.classList.add('hidden');
|
@@ -394,7 +553,62 @@
|
|
394 |
});
|
395 |
}
|
396 |
|
397 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
398 |
function createDebris(obstacle, count = 5) {
|
399 |
for (let i = 0; i < count; i++) {
|
400 |
gameState.debris.push({
|
@@ -415,8 +629,9 @@
|
|
415 |
function createBullet() {
|
416 |
if (gameState.ammo <= 0) return false; // 没有弹药了
|
417 |
|
418 |
-
const
|
419 |
-
const
|
|
|
420 |
const x = gameState.plane.x + 30; // 从飞机前端发射
|
421 |
const y = gameState.plane.y;
|
422 |
|
@@ -424,9 +639,16 @@
|
|
424 |
x,
|
425 |
y,
|
426 |
size,
|
427 |
-
speed
|
|
|
428 |
});
|
429 |
|
|
|
|
|
|
|
|
|
|
|
|
|
430 |
// 如果弹药不是无限的,减少弹药
|
431 |
if (gameState.ammo !== Infinity) {
|
432 |
gameState.ammo--;
|
@@ -448,14 +670,6 @@
|
|
448 |
});
|
449 |
}
|
450 |
|
451 |
-
// 更新UI
|
452 |
-
function updateUI() {
|
453 |
-
scoreDisplay.textContent = gameState.score;
|
454 |
-
livesDisplay.textContent = gameState.lives;
|
455 |
-
speedDisplay.textContent = Math.floor(gameState.speed);
|
456 |
-
ammoDisplay.textContent = gameState.ammo === Infinity ? "∞" : gameState.ammo;
|
457 |
-
}
|
458 |
-
|
459 |
// 检测碰撞
|
460 |
function checkCollision(rect1, rect2) {
|
461 |
return (
|
@@ -505,11 +719,28 @@
|
|
505 |
|
506 |
// 处理开火
|
507 |
if ((gameState.keys.Space || gameState.isFiring) &&
|
508 |
-
timestamp - gameState.plane.lastFireTime >
|
509 |
createBullet();
|
510 |
gameState.plane.lastFireTime = timestamp;
|
511 |
}
|
512 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
513 |
// 更新飞机速度
|
514 |
if (gameState.keys.ArrowUp || gameState.keys.ArrowDown) {
|
515 |
gameState.speed = Math.max(50,
|
@@ -571,6 +802,17 @@
|
|
571 |
gameState.lastCloudTime = timestamp;
|
572 |
}
|
573 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
574 |
// 更新碎片
|
575 |
gameState.debris.forEach(debris => {
|
576 |
debris.x += debris.speedX;
|
@@ -580,12 +822,118 @@
|
|
580 |
});
|
581 |
gameState.debris = gameState.debris.filter(debris => debris.opacity > 0);
|
582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
583 |
// 更新云朵
|
584 |
gameState.clouds.forEach(cloud => {
|
585 |
cloud.x -= cloud.speed;
|
586 |
});
|
587 |
gameState.clouds = gameState.clouds.filter(cloud => cloud.x + cloud.size > 0);
|
588 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
589 |
// 更新子弹
|
590 |
gameState.bullets.forEach(bullet => {
|
591 |
bullet.x += bullet.speed;
|
@@ -645,13 +993,21 @@
|
|
645 |
};
|
646 |
|
647 |
if (checkCollision(planeRect, obstacleRect) && !gameState.gameOver) {
|
648 |
-
|
649 |
-
gameState.
|
650 |
-
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
655 |
}
|
656 |
}
|
657 |
|
@@ -668,7 +1024,7 @@
|
|
668 |
};
|
669 |
|
670 |
if (checkCollision(bulletRect, obstacleRect)) {
|
671 |
-
obstacle.health
|
672 |
bulletHits.push(bulletIndex);
|
673 |
createExplosion(bullet.x, bullet.y);
|
674 |
|
@@ -758,6 +1114,70 @@
|
|
758 |
ctx.restore();
|
759 |
});
|
760 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
761 |
// 绘制子弹
|
762 |
gameState.bullets.forEach(bullet => {
|
763 |
const gradient = ctx.createRadialGradient(
|
@@ -922,6 +1342,22 @@
|
|
922 |
ctx.translate(gameState.plane.x, gameState.plane.y);
|
923 |
ctx.rotate(gameState.plane.rotation * Math.PI / 180);
|
924 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
925 |
// 飞机主体
|
926 |
ctx.beginPath();
|
927 |
ctx.moveTo(30, 0);
|
@@ -999,6 +1435,24 @@
|
|
999 |
ctx.restore();
|
1000 |
});
|
1001 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1002 |
// 绘制速度线
|
1003 |
if (gameState.speed > 120) {
|
1004 |
for (let i = 0; i < 10; i++) {
|
@@ -1034,6 +1488,17 @@
|
|
1034 |
}
|
1035 |
}
|
1036 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1037 |
// 事件监听
|
1038 |
window.addEventListener('resize', resizeCanvas);
|
1039 |
|
|
|
112 |
background-color: #777;
|
113 |
border-radius: 2px;
|
114 |
}
|
115 |
+
.powerup {
|
116 |
+
position: absolute;
|
117 |
+
width: 40px;
|
118 |
+
height: 40px;
|
119 |
+
border-radius: 50%;
|
120 |
+
display: flex;
|
121 |
+
align-items: center;
|
122 |
+
justify-content: center;
|
123 |
+
font-size: 20px;
|
124 |
+
text-shadow: 0 0 5px white;
|
125 |
+
}
|
126 |
+
.shield {
|
127 |
+
position: absolute;
|
128 |
+
border-radius: 50%;
|
129 |
+
border: 3px solid rgba(0, 204, 255, 0.6);
|
130 |
+
pointer-events: none;
|
131 |
+
}
|
132 |
+
.homing-missile {
|
133 |
+
position: absolute;
|
134 |
+
background: linear-gradient(to bottom, #ff5f5f, #ff0000);
|
135 |
+
border-radius: 50% 50% 0 0;
|
136 |
+
transform-origin: center bottom;
|
137 |
+
}
|
138 |
+
@keyframes pulse {
|
139 |
+
0% { transform: scale(1); opacity: 0.9; }
|
140 |
+
50% { transform: scale(1.1); opacity: 1; }
|
141 |
+
100% { transform: scale(1); opacity: 0.9; }
|
142 |
+
}
|
143 |
+
.powerup-effect {
|
144 |
+
position: absolute;
|
145 |
+
pointer-events: none;
|
146 |
+
animation: pulse 1.5s infinite;
|
147 |
+
}
|
148 |
</style>
|
149 |
</head>
|
150 |
<body class="bg-gray-900 text-white flex flex-col items-center justify-center h-screen">
|
|
|
155 |
<div id="startScreen" class="game-overlay flex flex-col items-center justify-center bg-black bg-opacity-70">
|
156 |
<h1 class="text-5xl font-bold mb-6 text-yellow-300">SKY ADVENTURE</h1>
|
157 |
<div class="plane text-6xl mb-8">✈️</div>
|
158 |
+
<p class="text-xl mb-8 text-center max-w-md px-4">控制飞机躲避障碍物<br>收集星星获得高分!<br>按射击按钮消灭障碍物!<br>收集道具获得特殊能力!</p>
|
159 |
<button id="startButton" class="bg-yellow-500 hover:bg-yellow-600 text-black font-bold py-3 px-8 rounded-full text-xl transition-all duration-300 transform hover:scale-105 pointer-events-auto">
|
160 |
开始游戏
|
161 |
</button>
|
162 |
+
<div class="mt-8 grid grid-cols-2 gap-4 text-left max-w-md px-8">
|
163 |
<div class="flex items-center">
|
164 |
+
<div class="powerup bg-red-500 mr-2"><i class="fas fa-bolt"></i></div>
|
165 |
+
<span>火力增强</span>
|
166 |
</div>
|
167 |
<div class="flex items-center">
|
168 |
+
<div class="powerup bg-purple-500 mr-2"><i class="fas fa-rocket"></i></div>
|
169 |
+
<span>跟踪导弹</span>
|
170 |
</div>
|
171 |
<div class="flex items-center">
|
172 |
+
<div class="powerup bg-blue-500 mr-2"><i class="fas fa-shield-alt"></i></div>
|
173 |
+
<span>保护罩</span>
|
|
|
174 |
</div>
|
175 |
<div class="flex items-center">
|
176 |
+
<div class="powerup bg-green-500 mr-2"><i class="fas fa-heart"></i></div>
|
177 |
+
<span>恢复生命</span>
|
178 |
</div>
|
179 |
</div>
|
180 |
</div>
|
|
|
207 |
</div>
|
208 |
</div>
|
209 |
|
210 |
+
<!-- 主动技能图标 -->
|
211 |
+
<div id="powerupStatus" class="absolute bottom-24 right-4 flex gap-2">
|
212 |
+
<!-- 这里会被JavaScript动态填充 -->
|
213 |
+
</div>
|
214 |
+
|
215 |
<!-- 触摸控制按钮 -->
|
216 |
<div id="leftBtn" class="control-btn hidden">
|
217 |
<i class="fas fa-arrow-left"></i>
|
|
|
260 |
velocity: 0, // 左右方向速度
|
261 |
verticalVelocity: 0, // 上下方向速度
|
262 |
rotation: 0,
|
263 |
+
lastFireTime: 0,
|
264 |
+
fireRate: 200, // 射击间隔(ms)
|
265 |
+
bulletDamage: 1, // 子弹伤害
|
266 |
+
hasShield: false,
|
267 |
+
shieldDuration: 0,
|
268 |
+
powerups: {
|
269 |
+
rapidFire: 0, // 火力增强
|
270 |
+
homingMissiles: 0, // 跟踪导弹
|
271 |
+
}
|
272 |
},
|
273 |
stars: [],
|
274 |
obstacles: [],
|
|
|
276 |
explosions: [],
|
277 |
bullets: [],
|
278 |
debris: [],
|
279 |
+
powerups: [], // 新增: 道具
|
280 |
+
homingMissiles: [], // 新增: 跟踪导弹
|
281 |
+
effects: [], // 新��: 特效
|
282 |
lastStarTime: 0,
|
283 |
lastObstacleTime: 0,
|
284 |
lastCloudTime: 0,
|
285 |
+
lastPowerupTime: 0, // 新增: 上次生成道具时间
|
286 |
keys: {
|
287 |
ArrowUp: false,
|
288 |
ArrowDown: false,
|
|
|
293 |
isMobile: false
|
294 |
};
|
295 |
|
296 |
+
// 道具类型
|
297 |
+
const POWERUP_TYPES = {
|
298 |
+
RAPID_FIRE: {
|
299 |
+
id: 'rapidFire',
|
300 |
+
icon: 'fas fa-bolt',
|
301 |
+
color: 'red',
|
302 |
+
duration: 10000, // 10秒
|
303 |
+
effect: (game) => {
|
304 |
+
game.plane.fireRate = 100; // 更快射击
|
305 |
+
game.plane.powerups.rapidFire = Date.now() + POWERUP_TYPES.RAPID_FIRE.duration;
|
306 |
+
createEffect('火力增强!', 'red', 1500);
|
307 |
+
}
|
308 |
+
},
|
309 |
+
HOMING_MISSILE: {
|
310 |
+
id: 'homingMissiles',
|
311 |
+
icon: 'fas fa-rocket',
|
312 |
+
color: 'purple',
|
313 |
+
duration: 10000, // 10秒
|
314 |
+
effect: (game) => {
|
315 |
+
game.plane.powerups.homingMissiles = Date.now() + POWERUP_TYPES.HOMING_MISSILE.duration;
|
316 |
+
createEffect('跟踪导弹已激活!', 'purple', 1500);
|
317 |
+
}
|
318 |
+
},
|
319 |
+
SHIELD: {
|
320 |
+
id: 'shield',
|
321 |
+
icon: 'fas fa-shield-alt',
|
322 |
+
color: 'blue',
|
323 |
+
duration: 8000, // 8秒
|
324 |
+
effect: (game) => {
|
325 |
+
game.plane.hasShield = true;
|
326 |
+
game.plane.shieldDuration = Date.now() + POWERUP_TYPES.SHIELD.duration;
|
327 |
+
createEffect('保护罩已启用!', 'blue', 1500);
|
328 |
+
}
|
329 |
+
},
|
330 |
+
HEALTH: {
|
331 |
+
id: 'health',
|
332 |
+
icon: 'fas fa-heart',
|
333 |
+
color: 'green',
|
334 |
+
effect: (game) => {
|
335 |
+
game.lives = Math.min(game.lives + 1, 5); // 最多5条命
|
336 |
+
updateUI();
|
337 |
+
createEffect('生命值恢复!', 'green', 1500);
|
338 |
+
}
|
339 |
+
}
|
340 |
+
};
|
341 |
+
|
342 |
// 获取DOM元素
|
343 |
const canvas = document.getElementById('gameCanvas');
|
344 |
const ctx = canvas.getContext('2d');
|
|
|
352 |
const ammoDisplay = document.getElementById('ammoDisplay');
|
353 |
const speedDisplay = document.getElementById('speedDisplay');
|
354 |
const finalScore = document.getElementById('finalScore');
|
355 |
+
const powerupStatus = document.getElementById('powerupStatus');
|
356 |
const leftBtn = document.getElementById('leftBtn');
|
357 |
const rightBtn = document.getElementById('rightBtn');
|
358 |
const upBtn = document.getElementById('upBtn');
|
|
|
374 |
}
|
375 |
}
|
376 |
|
377 |
+
// 创建文字特效
|
378 |
+
function createEffect(text, color, duration) {
|
379 |
+
gameState.effects.push({
|
380 |
+
text,
|
381 |
+
color,
|
382 |
+
x: gameState.plane.x,
|
383 |
+
y: gameState.plane.y - 50,
|
384 |
+
alpha: 1,
|
385 |
+
duration,
|
386 |
+
startTime: Date.now()
|
387 |
+
});
|
388 |
+
}
|
389 |
+
|
390 |
+
// 更新UI
|
391 |
+
function updateUI() {
|
392 |
+
scoreDisplay.textContent = gameState.score;
|
393 |
+
livesDisplay.textContent = gameState.lives;
|
394 |
+
speedDisplay.textContent = Math.floor(gameState.speed);
|
395 |
+
ammoDisplay.textContent = gameState.ammo === Infinity ? "∞" : gameState.ammo;
|
396 |
+
|
397 |
+
// 更新道具状态显示
|
398 |
+
powerupStatus.innerHTML = '';
|
399 |
+
|
400 |
+
if (gameState.plane.powerups.rapidFire > Date.now()) {
|
401 |
+
const timeLeft = Math.ceil((gameState.plane.powerups.rapidFire - Date.now()) / 1000);
|
402 |
+
powerupStatus.innerHTML += `
|
403 |
+
<div class="bg-black bg-opacity-50 px-3 py-1 rounded-lg flex items-center" title="火力增强 (${timeLeft}s)">
|
404 |
+
<i class="fas fa-bolt text-red-500 mr-2"></i>
|
405 |
+
</div>
|
406 |
+
`;
|
407 |
+
}
|
408 |
+
|
409 |
+
if (gameState.plane.powerups.homingMissiles > Date.now()) {
|
410 |
+
const timeLeft = Math.ceil((gameState.plane.powerups.homingMissiles - Date.now()) / 1000);
|
411 |
+
powerupStatus.innerHTML += `
|
412 |
+
<div class="bg-black bg-opacity-50 px-3 py-1 rounded-lg flex items-center" title="跟踪导弹 (${timeLeft}s)">
|
413 |
+
<i class="fas fa-rocket text-purple-500 mr-2"></i>
|
414 |
+
</div>
|
415 |
+
`;
|
416 |
+
}
|
417 |
+
|
418 |
+
if (gameState.plane.hasShield && gameState.plane.shieldDuration > Date.now()) {
|
419 |
+
const timeLeft = Math.ceil((gameState.plane.shieldDuration - Date.now()) / 1000);
|
420 |
+
powerupStatus.innerHTML += `
|
421 |
+
<div class="bg-black bg-opacity-50 px-3 py-1 rounded-lg flex items-center" title="保护罩 (${timeLeft}s)">
|
422 |
+
<i class="fas fa-shield-alt text-blue-500 mr-2"></i>
|
423 |
+
</div>
|
424 |
+
`;
|
425 |
+
}
|
426 |
+
}
|
427 |
+
|
428 |
// 初始化游戏
|
429 |
function initGame() {
|
430 |
gameState.isMobile = detectMobile();
|
|
|
444 |
velocity: 0,
|
445 |
verticalVelocity: 0,
|
446 |
rotation: 0,
|
447 |
+
lastFireTime: 0,
|
448 |
+
fireRate: 200,
|
449 |
+
bulletDamage: 1,
|
450 |
+
hasShield: false,
|
451 |
+
shieldDuration: 0,
|
452 |
+
powerups: {
|
453 |
+
rapidFire: 0,
|
454 |
+
homingMissiles: 0,
|
455 |
+
}
|
456 |
};
|
457 |
gameState.stars = [];
|
458 |
gameState.obstacles = [];
|
|
|
460 |
gameState.explosions = [];
|
461 |
gameState.bullets = [];
|
462 |
gameState.debris = [];
|
463 |
+
gameState.powerups = [];
|
464 |
+
gameState.homingMissiles = [];
|
465 |
+
gameState.effects = [];
|
466 |
gameState.lastStarTime = 0;
|
467 |
gameState.lastObstacleTime = 0;
|
468 |
gameState.lastCloudTime = 0;
|
469 |
+
gameState.lastPowerupTime = 0;
|
470 |
|
471 |
startScreen.classList.add('hidden');
|
472 |
gameOverScreen.classList.add('hidden');
|
|
|
553 |
});
|
554 |
}
|
555 |
|
556 |
+
// 创建道具
|
557 |
+
function createPowerup() {
|
558 |
+
const size = 40;
|
559 |
+
const x = canvas.width + size;
|
560 |
+
const y = Math.random() * (canvas.height - size * 2) + size;
|
561 |
+
const speed = Math.random() * 2 + 1;
|
562 |
+
|
563 |
+
// 随机选择一种道具类型
|
564 |
+
const powerupKeys = Object.keys(POWERUP_TYPES);
|
565 |
+
const randomPowerup = POWERUP_TYPES[powerupKeys[Math.floor(Math.random() * powerupKeys.length)]];
|
566 |
+
|
567 |
+
gameState.powerups.push({
|
568 |
+
x,
|
569 |
+
y,
|
570 |
+
size,
|
571 |
+
speed,
|
572 |
+
type: randomPowerup
|
573 |
+
});
|
574 |
+
}
|
575 |
+
|
576 |
+
// 创建跟踪导弹
|
577 |
+
function createHomingMissile() {
|
578 |
+
if (gameState.obstacles.length === 0) return; // 没有目标时不开火
|
579 |
+
|
580 |
+
// 找到最近的障碍物作为目标
|
581 |
+
let closestObstacle = null;
|
582 |
+
let minDistance = Infinity;
|
583 |
+
|
584 |
+
gameState.obstacles.forEach(obstacle => {
|
585 |
+
const dx = obstacle.x - gameState.plane.x;
|
586 |
+
const dy = obstacle.y - gameState.plane.y;
|
587 |
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
588 |
+
|
589 |
+
if (distance < minDistance) {
|
590 |
+
minDistance = distance;
|
591 |
+
closestObstacle = obstacle;
|
592 |
+
}
|
593 |
+
});
|
594 |
+
|
595 |
+
if (!closestObstacle) return;
|
596 |
+
|
597 |
+
const size = 12;
|
598 |
+
gameState.homingMissiles.push({
|
599 |
+
x: gameState.plane.x,
|
600 |
+
y: gameState.plane.y,
|
601 |
+
size,
|
602 |
+
speed: 8,
|
603 |
+
target: closestObstacle,
|
604 |
+
angle: Math.atan2(
|
605 |
+
closestObstacle.y - gameState.plane.y,
|
606 |
+
closestObstacle.x - gameState.plane.x
|
607 |
+
)
|
608 |
+
});
|
609 |
+
}
|
610 |
+
|
611 |
+
// 创建碎片
|
612 |
function createDebris(obstacle, count = 5) {
|
613 |
for (let i = 0; i < count; i++) {
|
614 |
gameState.debris.push({
|
|
|
629 |
function createBullet() {
|
630 |
if (gameState.ammo <= 0) return false; // 没有弹药了
|
631 |
|
632 |
+
const size = gameState.plane.powerups.rapidFire > Date.now() ? 10 : 8; // 火力增强时子弹更大
|
633 |
+
const damage = gameState.plane.powerups.rapidFire > Date.now() ? 2 : 1; // 火力增强时伤害更高
|
634 |
+
const speed = gameState.plane.powerups.rapidFire > Date.now() ? 18 : 15; // 火力增强时速度更快
|
635 |
const x = gameState.plane.x + 30; // 从飞机前端发射
|
636 |
const y = gameState.plane.y;
|
637 |
|
|
|
639 |
x,
|
640 |
y,
|
641 |
size,
|
642 |
+
speed,
|
643 |
+
damage
|
644 |
});
|
645 |
|
646 |
+
// 如果有跟踪导弹能力且冷却结束
|
647 |
+
if (gameState.plane.powerups.homingMissiles > Date.now() &&
|
648 |
+
Math.random() > 0.7) { // 70%概率发射跟踪导弹
|
649 |
+
requestAnimationFrame(createHomingMissile);
|
650 |
+
}
|
651 |
+
|
652 |
// 如果弹药不是无限的,减少弹药
|
653 |
if (gameState.ammo !== Infinity) {
|
654 |
gameState.ammo--;
|
|
|
670 |
});
|
671 |
}
|
672 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
673 |
// 检测碰撞
|
674 |
function checkCollision(rect1, rect2) {
|
675 |
return (
|
|
|
719 |
|
720 |
// 处理开火
|
721 |
if ((gameState.keys.Space || gameState.isFiring) &&
|
722 |
+
timestamp - gameState.plane.lastFireTime > gameState.plane.fireRate) { // 射击冷却
|
723 |
createBullet();
|
724 |
gameState.plane.lastFireTime = timestamp;
|
725 |
}
|
726 |
|
727 |
+
// 检查道具是否过期
|
728 |
+
if (gameState.plane.powerups.rapidFire > 0 && gameState.plane.powerups.rapidFire < Date.now()) {
|
729 |
+
gameState.plane.powerups.rapidFire = 0;
|
730 |
+
gameState.plane.fireRate = 200; // 恢复默认射击速度
|
731 |
+
createEffect('火力增强结束', 'red', 1500);
|
732 |
+
}
|
733 |
+
|
734 |
+
if (gameState.plane.powerups.homingMissiles > 0 && gameState.plane.powerups.homingMissiles < Date.now()) {
|
735 |
+
gameState.plane.powerups.homingMissiles = 0;
|
736 |
+
createEffect('跟踪导弹结束', 'purple', 1500);
|
737 |
+
}
|
738 |
+
|
739 |
+
if (gameState.plane.hasShield && gameState.plane.shieldDuration < Date.now()) {
|
740 |
+
gameState.plane.hasShield = false;
|
741 |
+
createEffect('保护罩消失', 'blue', 1500);
|
742 |
+
}
|
743 |
+
|
744 |
// 更新飞机速度
|
745 |
if (gameState.keys.ArrowUp || gameState.keys.ArrowDown) {
|
746 |
gameState.speed = Math.max(50,
|
|
|
802 |
gameState.lastCloudTime = timestamp;
|
803 |
}
|
804 |
|
805 |
+
// 生成新道具 (每5-8秒)
|
806 |
+
if (timestamp - gameState.lastPowerupTime > (Math.random() * 3000 + 5000) / gameState.difficulty) {
|
807 |
+
createPowerup();
|
808 |
+
gameState.lastPowerupTime = timestamp;
|
809 |
+
}
|
810 |
+
|
811 |
+
// 更新特效
|
812 |
+
gameState.effects = gameState.effects.filter(effect =>
|
813 |
+
Date.now() - effect.startTime < effect.duration
|
814 |
+
);
|
815 |
+
|
816 |
// 更新碎片
|
817 |
gameState.debris.forEach(debris => {
|
818 |
debris.x += debris.speedX;
|
|
|
822 |
});
|
823 |
gameState.debris = gameState.debris.filter(debris => debris.opacity > 0);
|
824 |
|
825 |
+
// 更新跟踪导弹
|
826 |
+
gameState.homingMissiles.forEach(missile => {
|
827 |
+
if (!missile.target || missile.target.hit) {
|
828 |
+
// 如果没有目标或目标已被击中,则直线飞行
|
829 |
+
missile.x += Math.cos(missile.angle) * missile.speed;
|
830 |
+
missile.y += Math.sin(missile.angle) * missile.speed;
|
831 |
+
} else {
|
832 |
+
// 计算新的角度以跟踪目标
|
833 |
+
const dx = missile.target.x - missile.x;
|
834 |
+
const dy = missile.target.y - missile.y;
|
835 |
+
const targetAngle = Math.atan2(dy, dx);
|
836 |
+
|
837 |
+
// 平滑转向
|
838 |
+
let angleDiff = targetAngle - missile.angle;
|
839 |
+
if (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
|
840 |
+
if (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
|
841 |
+
|
842 |
+
missile.angle += angleDiff * 0.1;
|
843 |
+
missile.x += Math.cos(missile.angle) * missile.speed;
|
844 |
+
missile.y += Math.sin(missile.angle) * missile.speed;
|
845 |
+
|
846 |
+
// 检测碰撞
|
847 |
+
const missileRect = {
|
848 |
+
x: missile.x - missile.size / 2,
|
849 |
+
y: missile.y - missile.size / 2,
|
850 |
+
width: missile.size,
|
851 |
+
height: missile.size
|
852 |
+
};
|
853 |
+
|
854 |
+
const targetRect = {
|
855 |
+
x: missile.target.x - missile.target.width / 2,
|
856 |
+
y: missile.target.y - missile.target.height / 2,
|
857 |
+
width: missile.target.width,
|
858 |
+
height: missile.target.height
|
859 |
+
};
|
860 |
+
|
861 |
+
if (checkCollision(missileRect, targetRect)) {
|
862 |
+
missile.hit = true;
|
863 |
+
missile.target.health -= 3; // 导弹伤害更高
|
864 |
+
|
865 |
+
if (missile.target.health <= 0) {
|
866 |
+
missile.target.hit = true;
|
867 |
+
gameState.score += missile.target.isLarge ? 30 : 15;
|
868 |
+
updateUI();
|
869 |
+
|
870 |
+
// 如果是大型障碍物,分裂成小型障碍物
|
871 |
+
if (missile.target.isLarge) {
|
872 |
+
for (let i = 0; i < 3; i++) {
|
873 |
+
gameState.obstacles.push({
|
874 |
+
x: missile.target.x + (Math.random() - 0.5) * 50,
|
875 |
+
y: missile.target.y + (Math.random() - 0.5) * 50,
|
876 |
+
width: missile.target.width / 2,
|
877 |
+
height: missile.target.height / 2,
|
878 |
+
speed: missile.target.speed * 1.2,
|
879 |
+
type: missile.target.type,
|
880 |
+
health: 1,
|
881 |
+
maxHealth: 1,
|
882 |
+
isLarge: false
|
883 |
+
});
|
884 |
+
}
|
885 |
+
}
|
886 |
+
|
887 |
+
// 创建碎片效果
|
888 |
+
createDebris(missile.target, 12);
|
889 |
+
}
|
890 |
+
|
891 |
+
// 创建爆炸效果
|
892 |
+
createExplosion(missile.x, missile.y);
|
893 |
+
}
|
894 |
+
}
|
895 |
+
});
|
896 |
+
gameState.homingMissiles = gameState.homingMissiles.filter(missile =>
|
897 |
+
missile.x < canvas.width && missile.x > 0 &&
|
898 |
+
missile.y < canvas.height && missile.y > 0 &&
|
899 |
+
!missile.hit
|
900 |
+
);
|
901 |
+
|
902 |
// 更新云朵
|
903 |
gameState.clouds.forEach(cloud => {
|
904 |
cloud.x -= cloud.speed;
|
905 |
});
|
906 |
gameState.clouds = gameState.clouds.filter(cloud => cloud.x + cloud.size > 0);
|
907 |
|
908 |
+
// 更新道具
|
909 |
+
gameState.powerups.forEach(powerup => {
|
910 |
+
powerup.x -= powerup.speed;
|
911 |
+
|
912 |
+
// 检测与飞机的碰撞
|
913 |
+
const planeRect = {
|
914 |
+
x: gameState.plane.x - gameState.plane.width / 2,
|
915 |
+
y: gameState.plane.y - gameState.plane.height / 2,
|
916 |
+
width: gameState.plane.width,
|
917 |
+
height: gameState.plane.height
|
918 |
+
};
|
919 |
+
|
920 |
+
const powerupRect = {
|
921 |
+
x: powerup.x - powerup.size / 2,
|
922 |
+
y: powerup.y - powerup.size / 2,
|
923 |
+
width: powerup.size,
|
924 |
+
height: powerup.size
|
925 |
+
};
|
926 |
+
|
927 |
+
if (checkCollision(planeRect, powerupRect) && !gameState.gameOver) {
|
928 |
+
powerup.collected = true;
|
929 |
+
powerup.type.effect(gameState);
|
930 |
+
updateUI();
|
931 |
+
}
|
932 |
+
});
|
933 |
+
gameState.powerups = gameState.powerups.filter(powerup =>
|
934 |
+
powerup.x + powerup.size > 0 && !powerup.collected
|
935 |
+
);
|
936 |
+
|
937 |
// 更新子弹
|
938 |
gameState.bullets.forEach(bullet => {
|
939 |
bullet.x += bullet.speed;
|
|
|
993 |
};
|
994 |
|
995 |
if (checkCollision(planeRect, obstacleRect) && !gameState.gameOver) {
|
996 |
+
// 如果有保护罩则不会受伤
|
997 |
+
if (!gameState.plane.hasShield || gameState.plane.shieldDuration < Date.now()) {
|
998 |
+
obstacle.hit = true;
|
999 |
+
gameState.lives--;
|
1000 |
+
updateUI();
|
1001 |
+
createExplosion(gameState.plane.x, gameState.plane.y);
|
1002 |
+
|
1003 |
+
if (gameState.lives <= 0) {
|
1004 |
+
endGame();
|
1005 |
+
}
|
1006 |
+
} else {
|
1007 |
+
// 保护罩被击中
|
1008 |
+
obstacle.hit = true;
|
1009 |
+
createExplosion(obstacle.x, obstacle.y);
|
1010 |
+
createDebris(obstacle, 4);
|
1011 |
}
|
1012 |
}
|
1013 |
|
|
|
1024 |
};
|
1025 |
|
1026 |
if (checkCollision(bulletRect, obstacleRect)) {
|
1027 |
+
obstacle.health -= bullet.damage;
|
1028 |
bulletHits.push(bulletIndex);
|
1029 |
createExplosion(bullet.x, bullet.y);
|
1030 |
|
|
|
1114 |
ctx.restore();
|
1115 |
});
|
1116 |
|
1117 |
+
// 绘制道具
|
1118 |
+
gameState.powerups.forEach(powerup => {
|
1119 |
+
ctx.save();
|
1120 |
+
ctx.translate(powerup.x, powerup.y);
|
1121 |
+
|
1122 |
+
// 绘制闪光效果
|
1123 |
+
ctx.beginPath();
|
1124 |
+
ctx.arc(0, 0, powerup.size / 2, 0, Math.PI * 2);
|
1125 |
+
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, powerup.size / 2);
|
1126 |
+
gradient.addColorStop(0, powerup.type.color);
|
1127 |
+
gradient.addColorStop(1, 'rgba(255,255,255,0)');
|
1128 |
+
ctx.fillStyle = gradient;
|
1129 |
+
ctx.globalAlpha = 0.3;
|
1130 |
+
ctx.fill();
|
1131 |
+
ctx.globalAlpha = 1;
|
1132 |
+
|
1133 |
+
// 绘制道具图标
|
1134 |
+
ctx.fillStyle = powerup.type.color;
|
1135 |
+
ctx.beginPath();
|
1136 |
+
ctx.arc(0, 0, powerup.size / 2 - 3, 0, Math.PI * 2);
|
1137 |
+
ctx.fill();
|
1138 |
+
|
1139 |
+
// 绘制边框
|
1140 |
+
ctx.strokeStyle = 'white';
|
1141 |
+
ctx.lineWidth = 2;
|
1142 |
+
ctx.stroke();
|
1143 |
+
|
1144 |
+
// 绘制道具图标
|
1145 |
+
ctx.fillStyle = 'white';
|
1146 |
+
ctx.font = '20px FontAwesome';
|
1147 |
+
ctx.textAlign = 'center';
|
1148 |
+
ctx.textBaseline = 'middle';
|
1149 |
+
ctx.fillText(String.fromCharCode(parseInt(getIconCode(powerup.type.icon), 16)), 0, 1);
|
1150 |
+
|
1151 |
+
ctx.restore();
|
1152 |
+
});
|
1153 |
+
|
1154 |
+
// 绘制跟踪导弹
|
1155 |
+
gameState.homingMissiles.forEach(missile => {
|
1156 |
+
ctx.save();
|
1157 |
+
ctx.translate(missile.x, missile.y);
|
1158 |
+
ctx.rotate(missile.angle);
|
1159 |
+
|
1160 |
+
// 导弹主体
|
1161 |
+
ctx.fillStyle = 'red';
|
1162 |
+
ctx.beginPath();
|
1163 |
+
ctx.moveTo(missile.size / 2, 0);
|
1164 |
+
ctx.lineTo(-missile.size / 2, -missile.size / 3);
|
1165 |
+
ctx.lineTo(-missile.size / 2, missile.size / 3);
|
1166 |
+
ctx.closePath();
|
1167 |
+
ctx.fill();
|
1168 |
+
|
1169 |
+
// 火焰效果
|
1170 |
+
ctx.fillStyle = 'orange';
|
1171 |
+
ctx.beginPath();
|
1172 |
+
ctx.moveTo(-missile.size / 2, -missile.size / 4);
|
1173 |
+
ctx.lineTo(-missile.size, 0);
|
1174 |
+
ctx.lineTo(-missile.size / 2, missile.size / 4);
|
1175 |
+
ctx.closePath();
|
1176 |
+
ctx.fill();
|
1177 |
+
|
1178 |
+
ctx.restore();
|
1179 |
+
});
|
1180 |
+
|
1181 |
// 绘制子弹
|
1182 |
gameState.bullets.forEach(bullet => {
|
1183 |
const gradient = ctx.createRadialGradient(
|
|
|
1342 |
ctx.translate(gameState.plane.x, gameState.plane.y);
|
1343 |
ctx.rotate(gameState.plane.rotation * Math.PI / 180);
|
1344 |
|
1345 |
+
// 绘制保护罩
|
1346 |
+
if (gameState.plane.hasShield && gameState.plane.shieldDuration > Date.now()) {
|
1347 |
+
ctx.beginPath();
|
1348 |
+
ctx.arc(0, 0, 45, 0, Math.PI * 2);
|
1349 |
+
ctx.strokeStyle = `rgba(0, 204, 255, ${0.3 + Math.sin(Date.now() / 200) * 0.3})`;
|
1350 |
+
ctx.lineWidth = 3;
|
1351 |
+
ctx.stroke();
|
1352 |
+
|
1353 |
+
// 保护罩光晕
|
1354 |
+
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 45);
|
1355 |
+
gradient.addColorStop(0, 'rgba(0, 204, 255, 0.2)');
|
1356 |
+
gradient.addColorStop(1, 'rgba(0, 204, 255, 0)');
|
1357 |
+
ctx.fillStyle = gradient;
|
1358 |
+
ctx.fill();
|
1359 |
+
}
|
1360 |
+
|
1361 |
// 飞机主体
|
1362 |
ctx.beginPath();
|
1363 |
ctx.moveTo(30, 0);
|
|
|
1435 |
ctx.restore();
|
1436 |
});
|
1437 |
|
1438 |
+
// 绘制特效文字
|
1439 |
+
gameState.effects.forEach(effect => {
|
1440 |
+
const timePassed = Date.now() - effect.startTime;
|
1441 |
+
const progress = timePassed / effect.duration;
|
1442 |
+
|
1443 |
+
ctx.save();
|
1444 |
+
ctx.translate(effect.x, effect.y - progress * 50); // 文字向上移动
|
1445 |
+
ctx.globalAlpha = 1 - progress * 0.8;
|
1446 |
+
|
1447 |
+
ctx.font = 'bold 20px Arial';
|
1448 |
+
ctx.fillStyle = effect.color;
|
1449 |
+
ctx.textAlign = 'center';
|
1450 |
+
ctx.textBaseline = 'middle';
|
1451 |
+
ctx.fillText(effect.text, 0, 0);
|
1452 |
+
|
1453 |
+
ctx.restore();
|
1454 |
+
});
|
1455 |
+
|
1456 |
// 绘制速度线
|
1457 |
if (gameState.speed > 120) {
|
1458 |
for (let i = 0; i < 10; i++) {
|
|
|
1488 |
}
|
1489 |
}
|
1490 |
|
1491 |
+
// 辅助函数: 获取FontAwesome图标的Unicode
|
1492 |
+
function getIconCode(iconClass) {
|
1493 |
+
const icons = {
|
1494 |
+
'fas fa-bolt': 'f0e7',
|
1495 |
+
'fas fa-rocket': 'f135',
|
1496 |
+
'fas fa-shield-alt': 'f3ed',
|
1497 |
+
'fas fa-heart': 'f004'
|
1498 |
+
};
|
1499 |
+
return icons[iconClass] || 'f128'; // 默认返回问号图标
|
1500 |
+
}
|
1501 |
+
|
1502 |
// 事件监听
|
1503 |
window.addEventListener('resize', resizeCanvas);
|
1504 |
|