debug / index.html
zdwalter's picture
Update index.html
547aeb0 verified
raw
history blame
83.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Sky Adventure - Plane Shooting Game</title>
<script src="https://cdn.tailwindcss.com"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<style>
body {
overflow: hidden;
touch-action: none;
margin: 0;
padding: 0;
}
#gameCanvas {
display: block;
background: linear-gradient(to bottom, #1e3c72 0%, #2a5298 100%);
}
.game-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
/* 云朵相关 */
.cloud {
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 50%;
}
@keyframes float {
0% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0px); }
}
/* 飞机抖动 */
.plane {
animation: float 2s ease-in-out infinite;
}
/* 星星闪烁 */
.star {
position: absolute;
color: gold;
text-shadow: 0 0 10px yellow;
animation: twinkle 1s ease-in-out infinite alternate;
}
@keyframes twinkle {
from { opacity: 0.7; transform: scale(0.9); }
to { opacity: 1; transform: scale(1.1); }
}
/* 障碍物与爆炸 */
.obstacle {
position: absolute;
background-color: #555;
border-radius: 5px;
}
.explosion {
position: absolute;
width: 60px;
height: 60px;
background: radial-gradient(circle, rgba(255,100,0,0.8) 0%, rgba(255,200,0,0.6) 50%, rgba(255,255,255,0) 70%);
border-radius: 50%;
animation: explode 0.5s ease-out forwards;
}
@keyframes explode {
0% { transform: scale(0); opacity: 1; }
100% { transform: scale(2); opacity: 0; }
}
/* 控制按钮样式 */
.control-btn {
position: absolute;
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: white;
pointer-events: auto;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.control-btn:active {
background: rgba(255, 255, 255, 0.4);
transform: scale(0.95);
}
#leftBtn {
bottom: 30px;
left: 30px;
}
#rightBtn {
bottom: 30px;
left: 110px;
}
#upBtn {
bottom: 100px;
right: 30px;
}
#downBtn {
bottom: 30px;
right: 30px;
}
#fireBtn {
bottom: 170px;
left: 30px;
}
/* 子弹 */
.bullet {
position: absolute;
background: linear-gradient(to right, #ff0, #f80);
border-radius: 50%;
}
/* 碎片 */
.debris {
position: absolute;
background-color: #777;
border-radius: 2px;
}
/* 道具 */
.powerup {
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
text-shadow: 0 0 5px white;
}
/* 护盾 */
.shield {
position: absolute;
border-radius: 50%;
border: 3px solid rgba(0, 204, 255, 0.6);
pointer-events: none;
}
/* 跟踪导弹 */
.homing-missile {
position: absolute;
background: linear-gradient(to bottom, #ff5f5f, #ff0000);
border-radius: 50% 50% 0 0;
transform-origin: center bottom;
}
/* 道具使用时发光 */
@keyframes pulse {
0% { transform: scale(1); opacity: 0.9; }
50% { transform: scale(1.1); opacity: 1; }
100% { transform: scale(1); opacity: 0.9; }
}
.powerup-effect {
position: absolute;
pointer-events: none;
animation: pulse 1.5s infinite;
}
/* 虚拟摇杆 */
.joystick {
position: absolute;
width: 100px;
height: 100px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
bottom: 30px;
left: 30px;
pointer-events: auto;
display: none;
}
.joystick-handle {
position: absolute;
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.4);
border-radius: 50%;
top: 30px;
left: 30px;
}
/* Boss 血条 */
.boss-health-bar {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
width: 200px;
height: 20px;
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
overflow: hidden;
}
.boss-health-fill {
height: 100%;
background: linear-gradient(to right, #ff0000, #ff9900);
width: 100%;
}
</style>
</head>
<body class="bg-gray-900 text-white flex flex-col items-center justify-center h-screen">
<div class="relative w-full h-full">
<canvas id="gameCanvas" class="w-full h-full"></canvas>
<!-- 开始界面 -->
<div id="startScreen" class="game-overlay flex flex-col items-center justify-center bg-black bg-opacity-70">
<h1 class="text-5xl font-bold mb-6 text-yellow-300">SKY ADVENTURE</h1>
<div class="plane text-6xl mb-8">✈️</div>
<p class="text-xl mb-8 text-center max-w-md px-4">
控制飞机躲避障碍物<br/>
收集星星获得高分!<br/>
按射击按钮消灭障碍物!<br/>
收集道具获得特殊能力!
</p>
<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"
>
开始游戏
</button>
<div class="mt-8 grid grid-cols-3 gap-4 text-left max-w-md px-8">
<div class="flex items-center">
<div class="powerup bg-red-500 mr-2"><i class="fas fa-bolt"></i></div>
<span>火力增强</span>
</div>
<div class="flex items-center">
<div class="powerup bg-purple-500 mr-2"><i class="fas fa-rocket"></i></div>
<span>跟踪导弹</span>
</div>
<div class="flex items-center">
<div class="powerup bg-blue-500 mr-2"><i class="fas fa-shield-alt"></i></div>
<span>保护罩</span>
</div>
<div class="flex items-center">
<div class="powerup bg-green-500 mr-2"><i class="fas fa-heart"></i></div>
<span>恢复生命</span>
</div>
<div class="flex items-center">
<div class="powerup bg-cyan-500 mr-2"><i class="fas fa-clock"></i></div>
<span>时间减速</span>
</div>
<div class="flex items-center">
<div class="powerup bg-orange-500 mr-2"><i class="fas fa-bomb"></i></div>
<span>清屏炸弹</span>
</div>
<div class="flex items-center">
<div class="powerup bg-pink-500 mr-2"><i class="fas fa-star"></i></div>
<span>双倍分数</span>
</div>
</div>
<div class="mt-4 text-sm text-gray-300">
最高分: <span id="highScoreDisplay">0</span>
</div>
</div>
<!-- 游戏UI -->
<div id="gameUI" class="game-overlay hidden">
<div class="absolute top-4 left-4 bg-black bg-opacity-50 px-4 py-2 rounded-lg">
<div class="flex items-center">
<i class="fas fa-star text-yellow-400 mr-2"></i>
<span id="scoreDisplay" class="text-xl">0</span>
</div>
</div>
<div class="absolute top-4 right-4 bg-black bg-opacity-50 px-4 py-2 rounded-lg">
<div class="flex items-center">
<i class="fas fa-heart text-red-500 mr-2"></i>
<span id="livesDisplay" class="text-xl">3</span>
</div>
</div>
<div class="absolute top-4 left-1/2 transform -translate-x-1/2 bg-black bg-opacity-50 px-4 py-2 rounded-lg">
<div class="flex items-center">
<i class="fas fa-bolt text-yellow-400 mr-2"></i>
<span id="ammoDisplay" class="text-xl"></span>
</div>
</div>
<div class="absolute bottom-4 left-4 bg-black bg-opacity-50 px-4 py-2 rounded-lg">
<div class="flex items-center">
<i class="fas fa-tachometer-alt text-blue-400 mr-2"></i>
<span id="speedDisplay" class="text-xl">100</span>
<span class="ml-1">km/h</span>
</div>
</div>
<!-- 主动技能图标 -->
<div id="powerupStatus" class="absolute bottom-24 right-4 flex gap-2">
<!-- 这里会被JavaScript动态填充 -->
</div>
<!-- Boss血条 -->
<div id="bossHealthBar" class="boss-health-bar hidden">
<div id="bossHealthFill" class="boss-health-fill"></div>
</div>
<!-- 触摸控制按钮:移动端可使用 -->
<div id="leftBtn" class="control-btn hidden">
<i class="fas fa-arrow-left"></i>
</div>
<div id="rightBtn" class="control-btn hidden">
<i class="fas fa-arrow-right"></i>
</div>
<div id="upBtn" class="control-btn hidden">
<i class="fas fa-arrow-up"></i>
</div>
<div id="downBtn" class="control-btn hidden">
<i class="fas fa-arrow-down"></i>
</div>
<!-- 开火按钮(默认在移动端显示) -->
<div id="fireBtn" class="control-btn hidden">
<i class="fas fa-bolt text-yellow-400"></i>
</div>
<!-- 虚拟摇杆(默认在移动端显示) -->
<div id="joystick" class="joystick hidden">
<div id="joystickHandle" class="joystick-handle"></div>
</div>
</div>
<!-- 游戏结束界面 -->
<div
id="gameOverScreen"
class="game-overlay hidden flex flex-col items-center justify-center bg-black bg-opacity-70"
>
<h1 class="text-5xl font-bold mb-6 text-red-500">GAME OVER</h1>
<div class="text-3xl mb-8">
得分:
<span id="finalScore" class="text-yellow-400">0</span>
</div>
<div id="achievements" class="mb-4 text-center">
<!-- 成就提示 -->
</div>
<button
id="restartButton"
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"
>
再玩一次
</button>
</div>
</div>
<!-- 音效文件 -->
<audio
id="shootSound"
src="https://assets.mixkit.co/sfx/preview/mixkit-laser-weapon-shot-1680.mp3"
preload="auto"
></audio>
<audio
id="explosionSound"
src="https://assets.mixkit.co/sfx/preview/mixkit-explosion-impact-1684.mp3"
preload="auto"
></audio>
<audio
id="powerupSound"
src="https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3"
preload="auto"
></audio>
<audio
id="bgMusic"
loop
src="https://assets.mixkit.co/music/preview/mixkit-game-show-suspense-waiting-668.mp3"
preload="auto"
></audio>
<script>
// =========================
// 游戏状态与配置
// =========================
const gameState = {
started: false,
gameOver: false,
score: 0,
lives: 3,
ammo: Infinity,
speed: 100,
difficulty: 1,
timeSlow: 0, // 时间减速结束时间
doubleScore: 0, // 双倍分数结束时间
bossActive: false,
bossHealth: 0,
bossMaxHealth: 0,
plane: {
x: 0,
y: 0,
width: 60,
height: 60,
velocity: 0,
verticalVelocity: 0,
rotation: 0,
lastFireTime: 0,
fireRate: 200,
bulletDamage: 1,
hasShield: false,
shieldDuration: 0,
powerups: {
rapidFire: 0,
homingMissiles: 0,
}
},
stars: [],
obstacles: [],
clouds: [],
explosions: [],
bullets: [],
debris: [],
powerups: [],
homingMissiles: [],
enemyBullets: [],
effects: [],
particles: [],
lastStarTime: 0,
lastObstacleTime: 0,
lastCloudTime: 0,
lastPowerupTime: 0,
lastBossSpawnTime: 0,
keys: {
ArrowUp: false,
ArrowDown: false,
ArrowLeft: false,
ArrowRight: false,
Space: false
},
isMobile: false,
joystickActive: false,
joystickAngle: 0,
joystickDistance: 0,
achievements: {
firstBlood: false,
combo5: false,
noDamage: false,
bossSlayer: false
},
highScore: localStorage.getItem('highScore') || 0
};
// =========================
// 常量配置
// =========================
const POWERUP_TYPES = {
RAPID_FIRE: {
id: 'rapidFire',
icon: 'fas fa-bolt',
color: 'red',
duration: 10000, // 10秒
effect: (game) => {
game.plane.fireRate = 100;
game.plane.powerups.rapidFire = Date.now() + POWERUP_TYPES.RAPID_FIRE.duration;
createEffect('火力增强!', 'red', 1500);
playSound('powerupSound');
}
},
HOMING_MISSILE: {
id: 'homingMissiles',
icon: 'fas fa-rocket',
color: 'purple',
duration: 10000, // 10秒
effect: (game) => {
game.plane.powerups.homingMissiles = Date.now() + POWERUP_TYPES.HOMING_MISSILE.duration;
createEffect('跟踪导弹已激活!', 'purple', 1500);
playSound('powerupSound');
}
},
SHIELD: {
id: 'shield',
icon: 'fas fa-shield-alt',
color: 'blue',
duration: 8000,
effect: (game) => {
game.plane.hasShield = true;
game.plane.shieldDuration = Date.now() + POWERUP_TYPES.SHIELD.duration;
createEffect('保护罩已启用!', 'blue', 1500);
playSound('powerupSound');
}
},
HEALTH: {
id: 'health',
icon: 'fas fa-heart',
color: 'green',
effect: (game) => {
game.lives = Math.min(game.lives + 1, 5); // 最多5条命
updateUI();
createEffect('生命值恢复!', 'green', 1500);
playSound('powerupSound');
}
},
TIME_SLOW: {
id: 'timeSlow',
icon: 'fas fa-clock',
color: 'cyan',
duration: 8000,
effect: (game) => {
game.timeSlow = Date.now() + POWERUP_TYPES.TIME_SLOW.duration;
createEffect('时间减速!', 'cyan', 1500);
playSound('powerupSound');
}
},
CLEAR_SCREEN: {
id: 'clearScreen',
icon: 'fas fa-bomb',
color: 'orange',
effect: (game) => {
// 清除所有障碍物
game.obstacles.forEach(obstacle => {
createExplosion(obstacle.x, obstacle.y);
createDebris(obstacle, 8);
});
game.obstacles = [];
createEffect('清屏炸弹!', 'orange', 1500);
playSound('powerupSound');
}
},
DOUBLE_SCORE: {
id: 'doubleScore',
icon: 'fas fa-star',
color: 'pink',
duration: 10000,
effect: (game) => {
game.doubleScore = Date.now() + POWERUP_TYPES.DOUBLE_SCORE.duration;
createEffect('双倍分数!', 'pink', 1500);
playSound('powerupSound');
}
}
};
// =========================
// 获取DOM元素
// =========================
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const startScreen = document.getElementById('startScreen');
const gameUI = document.getElementById('gameUI');
const gameOverScreen = document.getElementById('gameOverScreen');
const startButton = document.getElementById('startButton');
const restartButton = document.getElementById('restartButton');
const scoreDisplay = document.getElementById('scoreDisplay');
const livesDisplay = document.getElementById('livesDisplay');
const ammoDisplay = document.getElementById('ammoDisplay');
const speedDisplay = document.getElementById('speedDisplay');
const finalScore = document.getElementById('finalScore');
const powerupStatus = document.getElementById('powerupStatus');
const leftBtn = document.getElementById('leftBtn');
const rightBtn = document.getElementById('rightBtn');
const upBtn = document.getElementById('upBtn');
const downBtn = document.getElementById('downBtn');
const fireBtn = document.getElementById('fireBtn');
const joystick = document.getElementById('joystick');
const joystickHandle = document.getElementById('joystickHandle');
const bossHealthBar = document.getElementById('bossHealthBar');
const bossHealthFill = document.getElementById('bossHealthFill');
const achievementsDisplay = document.getElementById('achievements');
const highScoreDisplay = document.getElementById('highScoreDisplay');
// =========================
// 音效元素
// =========================
const shootSound = document.getElementById('shootSound');
const explosionSound = document.getElementById('explosionSound');
const powerupSound = document.getElementById('powerupSound');
const bgMusic = document.getElementById('bgMusic');
// =========================
// 音效相关
// =========================
function playSound(soundElement) {
if (soundElement === 'bgMusic') {
bgMusic.currentTime = 0;
bgMusic.play().catch(e => console.log('Autoplay prevented:', e));
} else {
const sound = document.getElementById(soundElement);
sound.currentTime = 0;
sound.play();
}
}
// =========================
// 检测是否移动设备
// =========================
function detectMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
// =========================
// 画布大小调整
// =========================
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (gameState.started && !gameState.gameOver) {
gameState.plane.x = canvas.width / 2;
gameState.plane.y = canvas.height / 2;
}
}
// =========================
// 文字与粒子特效相关
// =========================
function createEffect(text, color, duration) {
gameState.effects.push({
text,
color,
x: gameState.plane.x,
y: gameState.plane.y - 50,
alpha: 1,
duration,
startTime: Date.now()
});
}
function createParticles(x, y, count, color) {
for (let i = 0; i < count; i++) {
gameState.particles.push({
x,
y,
size: Math.random() * 5 + 2,
speedX: (Math.random() - 0.5) * 4,
speedY: (Math.random() - 0.5) * 4,
color,
life: 100
});
}
}
// =========================
// 更新UI
// =========================
function updateUI() {
scoreDisplay.textContent = gameState.score;
livesDisplay.textContent = gameState.lives;
speedDisplay.textContent = Math.floor(gameState.speed);
ammoDisplay.textContent = gameState.ammo === Infinity ? "∞" : gameState.ammo;
highScoreDisplay.textContent = gameState.highScore;
// 更新道具状态显示
powerupStatus.innerHTML = '';
if (gameState.plane.powerups.rapidFire > Date.now()) {
const timeLeft = Math.ceil((gameState.plane.powerups.rapidFire - Date.now()) / 1000);
powerupStatus.innerHTML += `
<div class="bg-black bg-opacity-50 px-3 py-1 rounded-lg flex items-center" title="火力增强 (${timeLeft}s)">
<i class="fas fa-bolt text-red-500 mr-2"></i>
</div>
`;
}
if (gameState.plane.powerups.homingMissiles > Date.now()) {
const timeLeft = Math.ceil((gameState.plane.powerups.homingMissiles - Date.now()) / 1000);
powerupStatus.innerHTML += `
<div class="bg-black bg-opacity-50 px-3 py-1 rounded-lg flex items-center" title="跟踪导弹 (${timeLeft}s)">
<i class="fas fa-rocket text-purple-500 mr-2"></i>
</div>
`;
}
if (gameState.plane.hasShield && gameState.plane.shieldDuration > Date.now()) {
const timeLeft = Math.ceil((gameState.plane.shieldDuration - Date.now()) / 1000);
powerupStatus.innerHTML += `
<div class="bg-black bg-opacity-50 px-3 py-1 rounded-lg flex items-center" title="保护罩 (${timeLeft}s)">
<i class="fas fa-shield-alt text-blue-500 mr-2"></i>
</div>
`;
}
if (gameState.timeSlow > Date.now()) {
const timeLeft = Math.ceil((gameState.timeSlow - Date.now()) / 1000);
powerupStatus.innerHTML += `
<div class="bg-black bg-opacity-50 px-3 py-1 rounded-lg flex items-center" title="时间减速 (${timeLeft}s)">
<i class="fas fa-clock text-cyan-500 mr-2"></i>
</div>
`;
}
if (gameState.doubleScore > Date.now()) {
const timeLeft = Math.ceil((gameState.doubleScore - Date.now()) / 1000);
powerupStatus.innerHTML += `
<div class="bg-black bg-opacity-50 px-3 py-1 rounded-lg flex items-center" title="双倍分数 (${timeLeft}s)">
<i class="fas fa-star text-pink-500 mr-2"></i>
</div>
`;
}
// Boss 血条
if (gameState.bossActive) {
bossHealthBar.classList.remove('hidden');
bossHealthFill.style.width = `${(gameState.bossHealth / gameState.bossMaxHealth) * 100}%`;
} else {
bossHealthBar.classList.add('hidden');
}
}
// =========================
// 初始化游戏
// =========================
function initGame() {
gameState.isMobile = detectMobile();
resizeCanvas();
gameState.started = true;
gameState.gameOver = false;
gameState.score = 0;
gameState.lives = 3;
gameState.ammo = Infinity;
gameState.speed = 100;
gameState.difficulty = 1;
gameState.timeSlow = 0;
gameState.doubleScore = 0;
gameState.bossActive = false;
gameState.bossHealth = 0;
gameState.bossMaxHealth = 0;
gameState.plane = {
x: canvas.width / 2,
y: canvas.height / 2,
width: 60,
height: 60,
velocity: 0,
verticalVelocity: 0,
rotation: 0,
lastFireTime: 0,
fireRate: 200,
bulletDamage: 1,
hasShield: false,
shieldDuration: 0,
powerups: {
rapidFire: 0,
homingMissiles: 0,
}
};
gameState.stars = [];
gameState.obstacles = [];
gameState.clouds = [];
gameState.explosions = [];
gameState.bullets = [];
gameState.debris = [];
gameState.powerups = [];
gameState.homingMissiles = [];
gameState.enemyBullets = [];
gameState.effects = [];
gameState.particles = [];
gameState.lastStarTime = 0;
gameState.lastObstacleTime = 0;
gameState.lastCloudTime = 0;
gameState.lastPowerupTime = 0;
gameState.lastBossSpawnTime = 0;
gameState.achievements = {
firstBlood: false,
combo5: false,
noDamage: false,
bossSlayer: false
};
startScreen.classList.add('hidden');
gameOverScreen.classList.add('hidden');
gameUI.classList.remove('hidden');
// 如果是移动设备,就只显示开火按钮和虚拟摇杆
if (gameState.isMobile) {
leftBtn.classList.add('hidden');
rightBtn.classList.add('hidden');
upBtn.classList.add('hidden');
downBtn.classList.add('hidden');
fireBtn.classList.remove('hidden');
joystick.classList.remove('hidden');
}
updateUI();
createInitialClouds();
playSound('bgMusic');
requestAnimationFrame(gameLoop);
}
function createInitialClouds() {
for (let i = 0; i < 10; i++) {
createCloud(true);
}
}
// =========================
// 创建各种元素的函数
// =========================
function createCloud(initial = false) {
const size = Math.random() * 60 + 40;
const x = initial ? Math.random() * canvas.width : canvas.width + size;
const y = Math.random() * canvas.height;
const speed = Math.random() * 1 + 0.5;
gameState.clouds.push({
x,
y,
size,
speed,
parts: Array(3).fill().map(() => ({
size: size * (Math.random() * 0.3 + 0.7),
offsetX: (Math.random() - 0.5) * size * 0.6,
offsetY: (Math.random() - 0.5) * size * 0.6
}))
});
}
function createStar() {
const size = Math.random() * 20 + 15;
const x = canvas.width + size;
const y = Math.random() * (canvas.height - size * 2) + size;
const speed = Math.random() * 3 + 3 + gameState.speed / 50;
gameState.stars.push({
x,
y,
size,
speed,
rotation: 0,
rotationSpeed: Math.random() * 0.1 - 0.05
});
}
function createObstacle() {
const width = Math.random() * 80 + 40;
const height = Math.random() * 80 + 40;
const x = canvas.width + width;
const y = Math.random() * (canvas.height - height);
const speed = Math.random() * 2 + 2 + gameState.speed / 50;
const type = Math.random() > 0.5 ? 'rectangle' : 'circle';
const health = type === 'rectangle' ? (width > 80 ? 3 : 2) : 1;
const collisionWidth = width * 1.2;
const collisionHeight = height * 1.2;
gameState.obstacles.push({
x,
y,
width,
height,
collisionWidth,
collisionHeight,
speed,
type,
health,
maxHealth: health,
isLarge: width > 80,
isBoss: false,
lastShotTime: 0
});
}
function createBoss() {
const width = 150;
const height = 150;
const x = canvas.width + width;
const y = canvas.height / 2 - height / 2;
const speed = 1 + gameState.speed / 100;
const health = 20 + Math.floor(gameState.score / 5000) * 5;
gameState.bossActive = true;
gameState.bossHealth = health;
gameState.bossMaxHealth = health;
gameState.lastBossSpawnTime = Date.now();
gameState.obstacles.push({
x,
y,
width,
height,
collisionWidth: width,
collisionHeight: height,
speed,
type: 'rectangle',
health,
maxHealth: health,
isLarge: true,
isBoss: true,
lastShotTime: 0
});
createEffect('BOSS出现!', 'red', 2000);
}
function createPowerup() {
const size = 40;
const x = canvas.width + size;
const y = Math.random() * (canvas.height - size * 2) + size;
const speed = Math.random() * 2 + 1;
const powerupKeys = Object.keys(POWERUP_TYPES);
const randomPowerup = POWERUP_TYPES[powerupKeys[Math.floor(Math.random() * powerupKeys.length)]];
gameState.powerups.push({
x,
y,
size,
speed,
type: randomPowerup
});
}
function createHomingMissile() {
if (gameState.obstacles.length === 0) return;
let closestObstacle = null;
let minDistance = Infinity;
gameState.obstacles.forEach(obstacle => {
const dx = obstacle.x - gameState.plane.x;
const dy = obstacle.y - gameState.plane.y;
const dist = Math.sqrt(dx*dx + dy*dy);
if (dist < minDistance) {
minDistance = dist;
closestObstacle = obstacle;
}
});
if (!closestObstacle) return;
const size = 12;
gameState.homingMissiles.push({
x: gameState.plane.x,
y: gameState.plane.y,
size,
speed: 8,
target: closestObstacle,
angle: Math.atan2(
closestObstacle.y - gameState.plane.y,
closestObstacle.x - gameState.plane.x
)
});
}
function createDebris(obstacle, count = 5) {
for (let i = 0; i < count; i++) {
gameState.debris.push({
x: obstacle.x,
y: obstacle.y,
width: obstacle.width / 3,
height: obstacle.height / 3,
speedX: (Math.random() - 0.5) * 4,
speedY: (Math.random() - 0.5) * 4,
rotation: 0,
rotationSpeed: (Math.random() - 0.5) * 0.2,
opacity: 1
});
}
}
function createBullet() {
if (gameState.ammo <= 0) return false;
const size = gameState.plane.powerups.rapidFire > Date.now() ? 10 : 8;
const damage = gameState.plane.powerups.rapidFire > Date.now() ? 2 : 1;
const speed = gameState.plane.powerups.rapidFire > Date.now() ? 18 : 15;
const x = gameState.plane.x + 30;
const y = gameState.plane.y;
gameState.bullets.push({
x,
y,
size,
speed,
damage
});
if (gameState.plane.powerups.homingMissiles > Date.now() && Math.random() > 0.7) {
requestAnimationFrame(createHomingMissile);
}
if (gameState.ammo !== Infinity) {
gameState.ammo--;
}
playSound('shootSound');
updateUI();
return true;
}
function createExplosion(x, y) {
gameState.explosions.push({
x,
y,
size: 0,
maxSize: Math.random() * 40 + 40,
alpha: 1
});
createParticles(x, y, 20, '#ff6600');
playSound('explosionSound');
}
function createEnemyBullet(obstacle) {
const angle = Math.atan2(gameState.plane.y - obstacle.y, gameState.plane.x - obstacle.x);
gameState.enemyBullets.push({
x: obstacle.x,
y: obstacle.y,
size: obstacle.isBoss ? 10 : 6,
speed: obstacle.isBoss ? 5 : 4,
angle,
damage: obstacle.isBoss ? 2 : 1,
hit: false
});
obstacle.lastShotTime = Date.now();
}
// =========================
// 碰撞检测
// =========================
function checkCollision(rect1, rect2) {
return (
rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y
);
}
// =========================
// 游戏结束
// =========================
function endGame() {
gameState.gameOver = true;
gameUI.classList.add('hidden');
gameOverScreen.classList.remove('hidden');
finalScore.textContent = gameState.score;
// 更新最高分
if (gameState.score > gameState.highScore) {
gameState.highScore = gameState.score;
localStorage.setItem('highScore', gameState.highScore);
achievementsDisplay.innerHTML += `<div class="text-yellow-400 mb-2">🎉 新纪录!</div>`;
}
// 显示成就(这里你可根据需求来自定义)
if (!gameState.achievements.firstBlood) {
achievementsDisplay.innerHTML += `<div class="text-green-400 mb-2">🏆 首次击杀!</div>`;
}
if (!gameState.achievements.combo5) {
achievementsDisplay.innerHTML += `<div class="text-blue-400 mb-2">🔥 连续5次击杀!</div>`;
}
if (!gameState.achievements.noDamage && gameState.lives === 3) {
achievementsDisplay.innerHTML += `<div class="text-purple-400 mb-2">🛡️ 无伤通关!</div>`;
}
if (!gameState.achievements.bossSlayer && gameState.bossActive) {
achievementsDisplay.innerHTML += `<div class="text-red-400 mb-2">👹 Boss杀手!</div>`;
}
// 隐藏手机端操作控件
leftBtn.classList.add('hidden');
rightBtn.classList.add('hidden');
upBtn.classList.add('hidden');
downBtn.classList.add('hidden');
fireBtn.classList.add('hidden');
joystick.classList.add('hidden');
bgMusic.pause();
}
// =========================
// 游戏主循环
// =========================
function gameLoop(timestamp) {
if (!gameState.started || gameState.gameOver) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
updateGame(timestamp);
drawGame();
requestAnimationFrame(gameLoop);
}
// =========================
// 更新游戏逻辑
// =========================
function updateGame(timestamp) {
// 难度随分数提升
gameState.difficulty = 1 + Math.min(gameState.score / 1000, 3);
// Boss 生成条件
if (!gameState.bossActive && gameState.score > 0 && gameState.score % 5000 === 0 && timestamp - gameState.lastBossSpawnTime > 30000) {
createBoss();
}
const timeSlowFactor = gameState.timeSlow > Date.now() ? 0.5 : 1;
// 玩家开火
if ((gameState.keys.Space || gameState.isFiring) && timestamp - gameState.plane.lastFireTime > gameState.plane.fireRate) {
createBullet();
gameState.plane.lastFireTime = timestamp;
}
// 道具过期判定
if (gameState.plane.powerups.rapidFire > 0 && gameState.plane.powerups.rapidFire < Date.now()) {
gameState.plane.powerups.rapidFire = 0;
gameState.plane.fireRate = 200;
createEffect('火力增强结束', 'red', 1500);
}
if (gameState.plane.powerups.homingMissiles > 0 && gameState.plane.powerups.homingMissiles < Date.now()) {
gameState.plane.powerups.homingMissiles = 0;
createEffect('跟踪导弹结束', 'purple', 1500);
}
if (gameState.plane.hasShield && gameState.plane.shieldDuration < Date.now()) {
gameState.plane.hasShield = false;
createEffect('保护罩消失', 'blue', 1500);
}
if (gameState.timeSlow > 0 && gameState.timeSlow < Date.now()) {
gameState.timeSlow = 0;
createEffect('时间恢复正常', 'cyan', 1500);
}
if (gameState.doubleScore > 0 && gameState.doubleScore < Date.now()) {
gameState.doubleScore = 0;
createEffect('双倍分数结束', 'pink', 1500);
}
// 上下加速
if (gameState.keys.ArrowUp || gameState.keys.ArrowDown) {
gameState.speed = Math.max(
50,
Math.min(200, gameState.speed + (gameState.keys.ArrowUp ? 0.5 : -0.5))
);
}
// 左右移动
if (gameState.keys.ArrowLeft || gameState.joystickAngle < -Math.PI/4) {
gameState.plane.rotation = Math.max(gameState.plane.rotation - 2, -20);
gameState.plane.velocity = Math.max(gameState.plane.velocity - 0.5, -5);
} else if (gameState.keys.ArrowRight || gameState.joystickAngle > Math.PI/4) {
gameState.plane.rotation = Math.min(gameState.plane.rotation + 2, 20);
gameState.plane.velocity = Math.min(gameState.plane.velocity + 0.5, 5);
} else {
gameState.plane.rotation *= 0.95;
gameState.plane.velocity *= 0.95;
if (Math.abs(gameState.plane.rotation) < 0.5) gameState.plane.rotation = 0;
if (Math.abs(gameState.plane.velocity) < 0.5) gameState.plane.velocity = 0;
}
// 上下移动
if (gameState.keys.ArrowUp || (gameState.joystickActive && gameState.joystickAngle < -Math.PI/4 && gameState.joystickAngle > -3*Math.PI/4)) {
gameState.plane.verticalVelocity = Math.max(gameState.plane.verticalVelocity - 0.5, -5);
} else if (gameState.keys.ArrowDown || (gameState.joystickActive && gameState.joystickAngle > Math.PI/4 && gameState.joystickAngle < 3*Math.PI/4)) {
gameState.plane.verticalVelocity = Math.min(gameState.plane.verticalVelocity + 0.5, 5);
} else {
gameState.plane.verticalVelocity *= 0.95;
if (Math.abs(gameState.plane.verticalVelocity) < 0.5) gameState.plane.verticalVelocity = 0;
}
// 飞机位置
gameState.plane.x += gameState.plane.velocity;
gameState.plane.y += gameState.plane.verticalVelocity;
gameState.plane.x = Math.max(gameState.plane.width / 2, Math.min(gameState.plane.x, canvas.width - gameState.plane.width / 2));
gameState.plane.y = Math.max(gameState.plane.height / 2, Math.min(gameState.plane.y, canvas.height - gameState.plane.height / 2));
// 生成元素
if (timestamp - gameState.lastStarTime > 2000 / gameState.difficulty) {
createStar();
gameState.lastStarTime = timestamp;
}
if (timestamp - gameState.lastObstacleTime > 1500 / gameState.difficulty * timeSlowFactor) {
createObstacle();
gameState.lastObstacleTime = timestamp;
}
if (timestamp - gameState.lastCloudTime > 1000 * timeSlowFactor) {
createCloud();
gameState.lastCloudTime = timestamp;
}
if (timestamp - gameState.lastPowerupTime > (Math.random() * 3000 + 5000) / gameState.difficulty * timeSlowFactor) {
createPowerup();
gameState.lastPowerupTime = timestamp;
}
// 粒子更新
gameState.particles.forEach(p => {
p.x += p.speedX;
p.y += p.speedY;
p.life--;
});
gameState.particles = gameState.particles.filter(p => p.life > 0);
// 文本特效更新
gameState.effects = gameState.effects.filter(effect => Date.now() - effect.startTime < effect.duration);
// 碎片更新
gameState.debris.forEach(d => {
d.x += d.speedX;
d.y += d.speedY;
d.rotation += d.rotationSpeed;
d.opacity -= 0.02;
});
gameState.debris = gameState.debris.filter(d => d.opacity > 0);
// 跟踪导弹更新
gameState.homingMissiles.forEach(missile => {
if (!missile.target || missile.target.hit) {
missile.x += Math.cos(missile.angle) * missile.speed;
missile.y += Math.sin(missile.angle) * missile.speed;
} else {
const dx = missile.target.x - missile.x;
const dy = missile.target.y - missile.y;
const targetAngle = Math.atan2(dy, dx);
let angleDiff = targetAngle - missile.angle;
if (angleDiff > Math.PI) angleDiff -= Math.PI*2;
if (angleDiff < -Math.PI) angleDiff += Math.PI*2;
missile.angle += angleDiff * 0.1;
missile.x += Math.cos(missile.angle) * missile.speed;
missile.y += Math.sin(missile.angle) * missile.speed;
const missileRect = {
x: missile.x - missile.size/2,
y: missile.y - missile.size/2,
width: missile.size,
height: missile.size
};
const targetRect = {
x: missile.target.x - missile.target.collisionWidth/2,
y: missile.target.y - missile.target.collisionHeight/2,
width: missile.target.collisionWidth,
height: missile.target.collisionHeight
};
if (checkCollision(missileRect, targetRect)) {
missile.hit = true;
missile.target.health -= 3;
if (missile.target.health <= 0) {
missile.target.hit = true;
const scoreBonus = missile.target.isBoss ? 500 : (missile.target.isLarge ? 30 : 15);
gameState.score += (gameState.doubleScore > Date.now() ? scoreBonus*2 : scoreBonus);
updateUI();
if (missile.target.isLarge && !missile.target.isBoss) {
for (let i=0;i<3;i++){
gameState.obstacles.push({
x: missile.target.x + (Math.random()-0.5)*50,
y: missile.target.y + (Math.random()-0.5)*50,
width: missile.target.width/2,
height: missile.target.height/2,
collisionWidth: missile.target.collisionWidth/2,
collisionHeight: missile.target.collisionHeight/2,
speed: missile.target.speed*1.2,
type: missile.target.type,
health: 1,
maxHealth: 1,
isLarge: false,
isBoss: false,
lastShotTime: 0
});
}
}
if (missile.target.isBoss) {
gameState.bossActive = false;
gameState.achievements.bossSlayer = true;
}
createDebris(missile.target, missile.target.isBoss ? 30 : 12);
}
createExplosion(missile.x, missile.y);
}
}
});
gameState.homingMissiles = gameState.homingMissiles.filter(m => m.x < canvas.width && m.x > 0 && m.y < canvas.height && m.y > 0 && !m.hit);
// 云更新
gameState.clouds.forEach(cloud => {
cloud.x -= cloud.speed * timeSlowFactor;
});
gameState.clouds = gameState.clouds.filter(cloud => cloud.x+cloud.size>0);
// 道具更新
gameState.powerups.forEach(pw => {
pw.x -= pw.speed * timeSlowFactor;
const planeRect = {
x: gameState.plane.x - gameState.plane.width/2,
y: gameState.plane.y - gameState.plane.height/2,
width: gameState.plane.width,
height: gameState.plane.height
};
const powerupRect = {
x: pw.x - pw.size/2,
y: pw.y - pw.size/2,
width: pw.size,
height: pw.size
};
if (checkCollision(planeRect, powerupRect) && !gameState.gameOver) {
pw.collected = true;
pw.type.effect(gameState);
updateUI();
createParticles(pw.x, pw.y, 15, pw.type.color);
}
});
gameState.powerups = gameState.powerups.filter(pw => pw.x+pw.size>0 && !pw.collected);
// 玩家子弹更新
gameState.bullets.forEach(b => {
b.x += b.speed * timeSlowFactor;
});
gameState.bullets = gameState.bullets.filter(b => b.x<canvas.width);
// 星星更新
gameState.stars.forEach(s => {
s.x -= s.speed * timeSlowFactor;
s.rotation += s.rotationSpeed;
const planeRect = {
x: gameState.plane.x - gameState.plane.width/2,
y: gameState.plane.y - gameState.plane.height/2,
width: gameState.plane.width,
height: gameState.plane.height
};
const starRect = {
x: s.x - s.size/2,
y: s.y - s.size/2,
width: s.size,
height: s.size
};
if (checkCollision(planeRect, starRect) && !gameState.gameOver) {
s.collected = true;
const scoreBonus = 10;
gameState.score += (gameState.doubleScore> Date.now()? scoreBonus*2 : scoreBonus);
updateUI();
createParticles(s.x, s.y, 10, 'gold');
}
});
gameState.stars = gameState.stars.filter(s => s.x + s.size>0 && !s.collected);
// 障碍物更新
let comboCount = 0;
gameState.obstacles.forEach(ob => {
ob.x -= ob.speed * timeSlowFactor;
// 敌人开火
if(!ob.hit && ob.x<canvas.width && ob.x>0){
const shotCooldown = ob.isBoss ? 1500 : 3000;
if(Date.now() - ob.lastShotTime>shotCooldown){
createEnemyBullet(ob);
}
}
// 碰撞检测:玩家飞机
const planeRect = {
x: gameState.plane.x - gameState.plane.width/2,
y: gameState.plane.y - gameState.plane.height/2,
width: gameState.plane.width,
height: gameState.plane.height
};
const obstacleRect = {
x: ob.x - ob.collisionWidth/2,
y: ob.y - ob.collisionHeight/2,
width: ob.collisionWidth,
height: ob.collisionHeight
};
if (checkCollision(planeRect, obstacleRect) && !gameState.gameOver) {
if (!gameState.plane.hasShield || gameState.plane.shieldDuration < Date.now()) {
ob.hit = true;
gameState.lives--;
updateUI();
createExplosion(gameState.plane.x, gameState.plane.y);
if (gameState.lives<=0) {
endGame();
}
} else {
ob.hit = true;
createExplosion(ob.x, ob.y);
createDebris(ob, 4);
}
}
// 碰撞检测:玩家子弹
if(!ob.hit){
const bulletHits = [];
gameState.bullets.forEach((bullet, idx) => {
const bulletRect = {
x: bullet.x - bullet.size/2,
y: bullet.y - bullet.size/2,
width: bullet.size,
height: bullet.size
};
if(checkCollision(bulletRect, obstacleRect)){
ob.health -= bullet.damage;
bulletHits.push(idx);
createExplosion(bullet.x, bullet.y);
if(ob.health<=0){
ob.hit= true;
const scoreBonus = ob.isBoss? 500 : (ob.isLarge?30:15);
gameState.score += (gameState.doubleScore> Date.now()? scoreBonus*2 : scoreBonus);
updateUI();
comboCount++;
if(ob.isLarge && !ob.isBoss){
for(let i=0;i<3;i++){
gameState.obstacles.push({
x: ob.x+(Math.random()-0.5)*50,
y: ob.y+(Math.random()-0.5)*50,
width: ob.width/2,
height: ob.height/2,
collisionWidth: ob.collisionWidth/2,
collisionHeight: ob.collisionHeight/2,
speed: ob.speed*1.2,
type: ob.type,
health: 1,
maxHealth: 1,
isLarge: false,
isBoss: false,
lastShotTime: 0
});
}
}
if(ob.isBoss){
gameState.bossActive= false;
gameState.achievements.bossSlayer= true;
}
createDebris(ob, ob.isBoss? 30: 8);
if(!gameState.achievements.firstBlood){
gameState.achievements.firstBlood= true;
createEffect('首次击杀!', 'green', 2000);
}
}
}
});
if(comboCount>=5 && !gameState.achievements.combo5){
gameState.achievements.combo5= true;
createEffect('连续5次击杀!', 'blue', 2000);
}
for(let i=bulletHits.length-1;i>=0;i--){
gameState.bullets.splice(bulletHits[i],1);
}
}
});
gameState.obstacles = gameState.obstacles.filter(o => o.x + o.width>0 && !o.hit);
// 敌人子弹更新
gameState.enemyBullets.forEach(bullet => {
bullet.x += Math.cos(bullet.angle) * bullet.speed * timeSlowFactor;
bullet.y += Math.sin(bullet.angle) * bullet.speed * timeSlowFactor;
const planeRect= {
x: gameState.plane.x - gameState.plane.width/2,
y: gameState.plane.y - gameState.plane.height/2,
width: gameState.plane.width,
height: gameState.plane.height
};
const bulletRect= {
x: bullet.x- bullet.size/2,
y: bullet.y- bullet.size/2,
width: bullet.size,
height: bullet.size
};
if(checkCollision(planeRect, bulletRect)){
if(!gameState.plane.hasShield || gameState.plane.shieldDuration< Date.now()){
gameState.lives -= bullet.damage;
createExplosion(bullet.x, bullet.y);
bullet.hit= true;
if(gameState.lives<=0){
endGame();
}else{
updateUI();
}
}else{
createExplosion(bullet.x, bullet.y);
bullet.hit= true;
}
}
if(bullet.x<0||bullet.x>canvas.width|| bullet.y<0|| bullet.y>canvas.height){
bullet.hit= true;
}
});
gameState.enemyBullets= gameState.enemyBullets.filter(b => !b.hit);
// 爆炸更新
gameState.explosions.forEach(ex => {
ex.size+= 2;
ex.alpha-=0.02;
});
gameState.explosions= gameState.explosions.filter(e => e.alpha>0);
}
// =========================
// 绘制游戏
// =========================
function drawGame() {
// 背景渐变
const gradient = ctx.createLinearGradient(0,0,0,canvas.height);
gradient.addColorStop(0, '#1e3c72');
gradient.addColorStop(1, '#2a5298');
ctx.fillStyle= gradient;
ctx.fillRect(0,0,canvas.width,canvas.height);
// 绘制云朵
gameState.clouds.forEach(cloud => {
cloud.parts.forEach(part => {
ctx.beginPath();
ctx.arc(cloud.x+ part.offsetX, cloud.y+ part.offsetY, part.size/2, 0, Math.PI*2);
ctx.fillStyle= `rgba(255,255,255,${0.7+ Math.random()*0.3})`;
ctx.fill();
});
});
// 粒子
gameState.particles.forEach(p => {
ctx.beginPath();
ctx.arc(p.x,p.y,p.size,0,Math.PI*2);
ctx.fillStyle= p.color;
ctx.globalAlpha= p.life/100;
ctx.fill();
ctx.globalAlpha= 1;
});
// 碎片
gameState.debris.forEach(d => {
ctx.save();
ctx.translate(d.x,d.y);
ctx.rotate(d.rotation);
ctx.fillStyle= `rgba(100,100,100,${d.opacity})`;
ctx.fillRect(-d.width/2, -d.height/2, d.width, d.height);
ctx.restore();
});
// 道具
gameState.powerups.forEach(pw => {
ctx.save();
ctx.translate(pw.x,pw.y);
ctx.beginPath();
ctx.arc(0,0, pw.size/2, 0, Math.PI*2);
const gradP= ctx.createRadialGradient(0,0,0,0,0,pw.size/2);
gradP.addColorStop(0, pw.type.color);
gradP.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle= gradP;
ctx.globalAlpha=0.3;
ctx.fill();
ctx.globalAlpha=1;
// 主体
ctx.fillStyle= pw.type.color;
ctx.beginPath();
ctx.arc(0,0,pw.size/2-3,0,Math.PI*2);
ctx.fill();
ctx.strokeStyle= 'white';
ctx.lineWidth=2;
ctx.stroke();
// 图标
ctx.fillStyle= 'white';
ctx.font= '20px FontAwesome';
ctx.textAlign= 'center';
ctx.textBaseline= 'middle';
ctx.fillText(String.fromCharCode(parseInt(getIconCode(pw.type.icon),16)),0,1);
ctx.restore();
});
// 跟踪导弹
gameState.homingMissiles.forEach(m => {
ctx.save();
ctx.translate(m.x,m.y);
ctx.rotate(m.angle);
// 主体
ctx.fillStyle= 'red';
ctx.beginPath();
ctx.moveTo(m.size/2,0);
ctx.lineTo(-m.size/2, -m.size/3);
ctx.lineTo(-m.size/2, m.size/3);
ctx.closePath();
ctx.fill();
// 火焰
ctx.fillStyle= 'orange';
ctx.beginPath();
ctx.moveTo(-m.size/2, -m.size/4);
ctx.lineTo(-m.size,0);
ctx.lineTo(-m.size/2,m.size/4);
ctx.closePath();
ctx.fill();
ctx.restore();
});
// 玩家子弹
gameState.bullets.forEach(b => {
const grad= ctx.createRadialGradient(b.x,b.y,0,b.x,b.y,b.size);
grad.addColorStop(0,'#ff0');
grad.addColorStop(1,'#f80');
ctx.beginPath();
ctx.arc(b.x,b.y,b.size,0,Math.PI*2);
ctx.fillStyle= grad;
ctx.fill();
// 尾迹
ctx.beginPath();
ctx.moveTo(b.x- b.speed, b.y);
ctx.lineTo(b.x,b.y);
ctx.strokeStyle= 'rgba(255,200,0,0.8)';
ctx.lineWidth= b.size/2;
ctx.stroke();
});
// 敌人子弹
gameState.enemyBullets.forEach(b => {
ctx.save();
ctx.translate(b.x,b.y);
ctx.beginPath();
ctx.arc(0,0,b.size,0,Math.PI*2);
ctx.fillStyle= 'rgba(255,0,0,0.8)';
ctx.fill();
ctx.restore();
});
// 星星
gameState.stars.forEach(s => {
ctx.save();
ctx.translate(s.x,s.y);
ctx.rotate(s.rotation);
ctx.beginPath();
for(let i=0;i<5;i++){
const angle = (i*2*Math.PI/5)-Math.PI/2;
const innerAngle= angle + Math.PI/5;
const outerRadius= s.size/2;
const innerRadius= s.size/4;
if(i===0){
ctx.moveTo(Math.cos(angle)*outerRadius, Math.sin(angle)* outerRadius);
}else{
ctx.lineTo(Math.cos(angle)*outerRadius, Math.sin(angle)* outerRadius);
}
ctx.lineTo(Math.cos(innerAngle)* innerRadius, Math.sin(innerAngle)* innerRadius);
}
ctx.closePath();
const gradStar= ctx.createRadialGradient(0,0,0,0,0, s.size/2);
gradStar.addColorStop(0,'gold');
gradStar.addColorStop(1,'yellow');
ctx.fillStyle= gradStar;
ctx.shadowColor= 'yellow';
ctx.shadowBlur=10;
ctx.fill();
ctx.restore();
});
// 障碍物(敌人)
gameState.obstacles.forEach(ob => {
ctx.save();
ctx.translate(ob.x, ob.y);
if(ob.type==='rectangle'){
if(ob.isBoss){
// Boss
if(ob.health< ob.maxHealth){
const w= 100;
ctx.fillStyle= 'red';
ctx.fillRect(-w/2, -ob.height/2-15, w,5);
ctx.fillStyle= 'purple';
ctx.fillRect(-w/2, -ob.height/2-15, w*(ob.health/ ob.maxHealth),5);
}
ctx.fillStyle= '#8B0000';
ctx.fillRect(-ob.width/2, -ob.height/2, ob.width, ob.height);
ctx.fillStyle= '#600000';
ctx.fillRect(-ob.width/2+5, -ob.height/2+5, ob.width-10, ob.height-10);
if(ob.health< ob.maxHealth){
ctx.strokeStyle= 'rgba(0,0,0,0.5)';
ctx.lineWidth=2;
for(let i=0;i<10;i++){
ctx.beginPath();
ctx.moveTo(-ob.width/2 + Math.random()* ob.width, -ob.height/2+ Math.random()*(ob.height/3));
ctx.lineTo(-ob.width/2+ Math.random()* ob.width, ob.height/2- Math.random()*(ob.height/3));
ctx.stroke();
}
}
ctx.fillStyle= 'gold';
ctx.font= 'bold 20px Arial';
ctx.textAlign= 'center';
ctx.textBaseline= 'middle';
ctx.fillText('BOSS',0,0);
} else {
// 小飞机造型的敌人
if(ob.health< ob.maxHealth){
const w= 20;
ctx.fillStyle= 'red';
ctx.fillRect(-w/2, -ob.height/2-10, w,4);
ctx.fillStyle= 'lime';
ctx.fillRect(-w/2, -ob.height/2-10, w*(ob.health/ ob.maxHealth),4);
}
ctx.save();
// 敌机主体(蓝色)
ctx.beginPath();
ctx.moveTo(20,0);
ctx.lineTo(-15,-12);
ctx.lineTo(-20,0);
ctx.lineTo(-15,12);
ctx.closePath();
ctx.fillStyle= '#3498db';
ctx.fill();
// 窗户
ctx.beginPath();
ctx.arc(5,0,4,0,Math.PI*2);
ctx.fillStyle= '#fff';
ctx.fill();
// 机翼
ctx.beginPath();
ctx.moveTo(3,0);
ctx.lineTo(-5,-15);
ctx.lineTo(-12,-15);
ctx.lineTo(-5,0);
ctx.closePath();
ctx.fillStyle='#2980b9';
ctx.fill();
ctx.beginPath();
ctx.moveTo(3,0);
ctx.lineTo(-5,15);
ctx.lineTo(-12,15);
ctx.lineTo(-5,0);
ctx.closePath();
ctx.fillStyle='#2980b9';
ctx.fill();
// 尾翼
ctx.beginPath();
ctx.moveTo(-15,0);
ctx.lineTo(-20,-8);
ctx.lineTo(-25,-8);
ctx.lineTo(-20,0);
ctx.closePath();
ctx.fillStyle= '#1c5982';
ctx.fill();
ctx.beginPath();
ctx.moveTo(-15,0);
ctx.lineTo(-20,8);
ctx.lineTo(-25,8);
ctx.lineTo(-20,0);
ctx.closePath();
ctx.fillStyle= '#1c5982';
ctx.fill();
ctx.restore();
}
} else {
// UFO
if(ob.health< ob.maxHealth){
const w= 20;
ctx.fillStyle= 'red';
ctx.fillRect(-w/2, -ob.height/2-10, w,4);
ctx.fillStyle= 'lime';
ctx.fillRect(-w/2, -ob.height/2-10, w*(ob.health/ ob.maxHealth),4);
}
// 顶部圆弧
ctx.beginPath();
ctx.ellipse(0, -ob.height/6, ob.width/3, ob.height/4, 0, 0, Math.PI*2);
ctx.fillStyle= '#9b59b6';
ctx.fill();
// 主体
ctx.beginPath();
ctx.ellipse(0,0, ob.width/2, ob.height/3,0,0,Math.PI*2);
ctx.fillStyle= '#8e44ad';
ctx.fill();
// 灯
for(let i=0;i<3;i++){
const angle= (i/3)*Math.PI*2;
const rx= Math.cos(angle)*(ob.width/2.2);
const ry= Math.sin(angle)*(ob.height/3.2);
ctx.beginPath();
ctx.arc(rx,ry,3,0,Math.PI*2);
ctx.fillStyle= '#ffdc00';
ctx.fill();
}
}
ctx.restore();
});
// 玩家飞机
ctx.save();
ctx.translate(gameState.plane.x, gameState.plane.y);
ctx.rotate(gameState.plane.rotation * Math.PI/180);
if(gameState.plane.hasShield && gameState.plane.shieldDuration> Date.now()){
ctx.beginPath();
ctx.arc(0,0,45,0,Math.PI*2);
ctx.strokeStyle= `rgba(0,204,255,${0.3+ Math.sin(Date.now()/200)*0.3})`;
ctx.lineWidth= 3;
ctx.stroke();
const gradS= ctx.createRadialGradient(0,0,0,0,0,45);
gradS.addColorStop(0,'rgba(0,204,255,0.2)');
gradS.addColorStop(1,'rgba(0,204,255,0)');
ctx.fillStyle= gradS;
ctx.fill();
}
// 主体
ctx.beginPath();
ctx.moveTo(30,0);
ctx.lineTo(-20,-15);
ctx.lineTo(-25,0);
ctx.lineTo(-20,15);
ctx.closePath();
ctx.fillStyle= '#e74c3c';
ctx.fill();
// 窗户
ctx.beginPath();
ctx.arc(10,0,5,0,Math.PI*2);
ctx.fillStyle= '#3498db';
ctx.fill();
// 翼
ctx.beginPath();
ctx.moveTo(5,0);
ctx.lineTo(-5,-20);
ctx.lineTo(-15,-20);
ctx.lineTo(-5,0);
ctx.closePath();
ctx.fillStyle= '#c0392b';
ctx.fill();
ctx.beginPath();
ctx.moveTo(5,0);
ctx.lineTo(-5,20);
ctx.lineTo(-15,20);
ctx.lineTo(-5,0);
ctx.closePath();
ctx.fillStyle= '#c0392b';
ctx.fill();
// 尾翼
ctx.beginPath();
ctx.moveTo(-20,0);
ctx.lineTo(-25,-10);
ctx.lineTo(-30,-10);
ctx.lineTo(-25,0);
ctx.closePath();
ctx.fillStyle= '#a5281b';
ctx.fill();
ctx.beginPath();
ctx.moveTo(-20,0);
ctx.lineTo(-25,10);
ctx.lineTo(-30,10);
ctx.lineTo(-25,0);
ctx.closePath();
ctx.fillStyle= '#a5281b';
ctx.fill();
ctx.restore();
// 爆炸
gameState.explosions.forEach(ex => {
ctx.save();
ctx.translate(ex.x, ex.y);
const gradExp= ctx.createRadialGradient(0,0,0,0,0, ex.size);
gradExp.addColorStop(0, `rgba(255,100,0,${ex.alpha})`);
gradExp.addColorStop(0.5, `rgba(255,200,0,${ex.alpha*0.6})`);
gradExp.addColorStop(1, `rgba(255,255,255,0)`);
ctx.beginPath();
ctx.arc(0,0, ex.size, 0, Math.PI*2);
ctx.fillStyle= gradExp;
ctx.fill();
ctx.restore();
});
// 文字特效
gameState.effects.forEach(effect => {
const timePassed= Date.now()- effect.startTime;
const progress= timePassed / effect.duration;
ctx.save();
ctx.translate(effect.x, effect.y- progress*50);
ctx.globalAlpha= 1- progress*0.8;
ctx.font= 'bold 20px Arial';
ctx.fillStyle= effect.color;
ctx.textAlign= 'center';
ctx.textBaseline= 'middle';
ctx.fillText(effect.text, 0,0);
ctx.restore();
});
// 速度线
if(gameState.speed>120){
for(let i=0;i<10;i++){
const x= Math.random()* canvas.width;
const y= Math.random()* canvas.height;
const length= Math.random()*20 +10;
const angle= Math.atan2(gameState.plane.y- y, gameState.plane.x- x);
ctx.save();
ctx.translate(x,y);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(length,0);
ctx.strokeStyle= `rgba(255,255,255,${Math.random()*0.5+0.3})`;
ctx.lineWidth=1;
ctx.stroke();
ctx.restore();
}
}
// 难度提示
if(gameState.difficulty>1.5){
ctx.fillStyle= 'rgba(255,0,0,0.5)';
ctx.font= '20px Arial';
ctx.textAlign= 'right';
ctx.fillText(`难度: ${gameState.difficulty.toFixed(1)}x`, canvas.width-20,30);
}
}
// =========================
// 获取FontAwesome图标码
// =========================
function getIconCode(iconClass) {
const icons = {
'fas fa-bolt': 'f0e7',
'fas fa-rocket': 'f135',
'fas fa-shield-alt': 'f3ed',
'fas fa-heart': 'f004',
'fas fa-clock': 'f017',
'fas fa-bomb': 'f1e2',
'fas fa-star': 'f005'
};
return icons[iconClass] || 'f128';
}
// =========================
// 虚拟摇杆控制逻辑
// =========================
function setupJoystick() {
const joystickArea= joystick;
const handle= joystickHandle;
let active= false;
let startX=0, startY=0;
let handleX=0, handleY=0;
const maxDistance= 40;
joystickArea.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch= e.touches[0];
const rect= joystickArea.getBoundingClientRect();
startX= rect.left+ rect.width/2;
startY= rect.top+ rect.height/2;
handleX= touch.clientX- startX;
handleY= touch.clientY- startY;
const dist= Math.sqrt(handleX*handleX+ handleY*handleY);
if(dist> maxDistance){
handleX= (handleX/dist)* maxDistance;
handleY= (handleY/dist)* maxDistance;
}
handle.style.transform= `translate(${handleX}px, ${handleY}px)`;
gameState.joystickAngle= Math.atan2(handleY, handleX);
gameState.joystickDistance= dist/maxDistance;
gameState.joystickActive= true;
active= true;
});
joystickArea.addEventListener('touchmove', (e) => {
if(!active) return;
e.preventDefault();
const touch= e.touches[0];
handleX= touch.clientX- startX;
handleY= touch.clientY- startY;
const dist= Math.sqrt(handleX*handleX+ handleY*handleY);
if(dist> maxDistance){
handleX= (handleX/dist)* maxDistance;
handleY= (handleY/dist)* maxDistance;
}
handle.style.transform= `translate(${handleX}px, ${handleY}px)`;
gameState.joystickAngle= Math.atan2(handleY, handleX);
gameState.joystickDistance= dist/maxDistance;
});
joystickArea.addEventListener('touchend', (e) => {
e.preventDefault();
handle.style.transform= 'translate(0,0)';
gameState.joystickActive= false;
active= false;
});
}
// =========================
// 事件监听 & 入口
// =========================
window.addEventListener('resize', resizeCanvas);
document.addEventListener('keydown', (e) => {
if(gameState.keys.hasOwnProperty(e.key)){
gameState.keys[e.key]= true;
e.preventDefault();
}
if(e.key=== ' '|| e.key=== 'Spacebar'){
gameState.keys.Space= true;
e.preventDefault();
}
});
document.addEventListener('keyup', (e) => {
if(gameState.keys.hasOwnProperty(e.key)){
gameState.keys[e.key]= false;
e.preventDefault();
}
if(e.key=== ' '|| e.key=== 'Spacebar'){
gameState.keys.Space= false;
e.preventDefault();
}
});
// 触摸按钮
leftBtn.addEventListener('touchstart', (e) => { e.preventDefault(); gameState.keys.ArrowLeft= true; });
leftBtn.addEventListener('touchend', (e) => { e.preventDefault(); gameState.keys.ArrowLeft= false; });
rightBtn.addEventListener('touchstart', (e) => { e.preventDefault(); gameState.keys.ArrowRight= true; });
rightBtn.addEventListener('touchend', (e) => { e.preventDefault(); gameState.keys.ArrowRight= false; });
upBtn.addEventListener('touchstart', (e) => { e.preventDefault(); gameState.keys.ArrowUp= true; });
upBtn.addEventListener('touchend', (e) => { e.preventDefault(); gameState.keys.ArrowUp= false; });
downBtn.addEventListener('touchstart', (e) => { e.preventDefault(); gameState.keys.ArrowDown= true; });
downBtn.addEventListener('touchend', (e) => { e.preventDefault(); gameState.keys.ArrowDown= false; });
fireBtn.addEventListener('touchstart', (e) => { e.preventDefault(); gameState.isFiring= true; });
fireBtn.addEventListener('touchend', (e) => { e.preventDefault(); gameState.isFiring= false; });
// 鼠标点击(在桌面上可测试)
leftBtn.addEventListener('mousedown', (e) => { e.preventDefault(); gameState.keys.ArrowLeft= true; });
leftBtn.addEventListener('mouseup', (e) => { e.preventDefault(); gameState.keys.ArrowLeft= false; });
leftBtn.addEventListener('mouseleave', (e) => { e.preventDefault(); gameState.keys.ArrowLeft= false; });
rightBtn.addEventListener('mousedown', (e) => { e.preventDefault(); gameState.keys.ArrowRight= true; });
rightBtn.addEventListener('mouseup', (e) => { e.preventDefault(); gameState.keys.ArrowRight= false; });
rightBtn.addEventListener('mouseleave', (e) => { e.preventDefault(); gameState.keys.ArrowRight= false; });
upBtn.addEventListener('mousedown', (e) => { e.preventDefault(); gameState.keys.ArrowUp= true; });
upBtn.addEventListener('mouseup', (e) => { e.preventDefault(); gameState.keys.ArrowUp= false; });
upBtn.addEventListener('mouseleave', (e) => { e.preventDefault(); gameState.keys.ArrowUp= false; });
downBtn.addEventListener('mousedown', (e) => { e.preventDefault(); gameState.keys.ArrowDown= true; });
downBtn.addEventListener('mouseup', (e) => { e.preventDefault(); gameState.keys.ArrowDown= false; });
downBtn.addEventListener('mouseleave', (e) => { e.preventDefault(); gameState.keys.ArrowDown= false; });
fireBtn.addEventListener('mousedown', (e) => { e.preventDefault(); gameState.isFiring= true; });
fireBtn.addEventListener('mouseup', (e) => { e.preventDefault(); gameState.isFiring= false; });
fireBtn.addEventListener('mouseleave', (e) => { e.preventDefault(); gameState.isFiring= false; });
startButton.addEventListener('click', initGame);
restartButton.addEventListener('click', initGame);
resizeCanvas();
setupJoystick();
highScoreDisplay.textContent = gameState.highScore;
</script>
<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;">
Made with
<img
src="https://enzostvs-deepsite.hf.space/logo.svg"
alt="DeepSite Logo"
style="width:16px; height:16px; vertical-align:middle; display:inline-block; margin-right:3px; filter:brightness(0) invert(1);"
/>
<a
href="https://enzostvs-deepsite.hf.space"
style="color:#fff; text-decoration:underline;"
target="_blank"
>DeepSite</a
>
-
<a
href="https://enzostvs-deepsite.hf.space?remix=zdwalter/plane-fighter-2"
style="color:#fff; text-decoration:underline;"
target="_blank"
>🧬 Remix</a
>
</p>
</body>
</html>