debug / index.html
zdwalter's picture
Update index.html
66bddec verified
raw
history blame contribute delete
72.3 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%;
}
.enemy-bullet {
position: absolute;
background: linear-gradient(to right, #f00, #800);
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-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>
<!-- 加载陨石贴图(请替换src为你自己喜欢的图片地址) -->
<script>
const meteorImage = new Image();
meteorImage.src = "https://via.placeholder.com/80x80.png?text=Meteor";
</script>
<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>
<audio id="enemyShootSound" src="https://assets.mixkit.co/sfx/preview/mixkit-short-laser-gun-shot-1670.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, // Boss是否激活
bossHealth: 0, // Boss当前血量
bossMaxHealth: 0, // Boss最大血量
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: [],
enemyBullets: [], // 敌人子弹
debris: [],
powerups: [],
homingMissiles: [],
effects: [],
particles: [],
lastStarTime: 0,
lastObstacleTime: 0,
lastCloudTime: 0,
lastPowerupTime: 0,
lastBossSpawnTime: 0,
lastEnemyShootTime: 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,
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,
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);
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');
const enemyShootSound = document.getElementById('enemyShootSound');
// 播放音效
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.enemyBullets = [];
gameState.debris = [];
gameState.powerups = [];
gameState.homingMissiles = [];
gameState.effects = [];
gameState.particles = [];
gameState.lastStarTime = 0;
gameState.lastObstacleTime = 0;
gameState.lastCloudTime = 0;
gameState.lastPowerupTime = 0;
gameState.lastBossSpawnTime = 0;
gameState.lastEnemyShootTime = 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
});
}
// 创建敌人子弹(增强伤害,将 damage 调整为2)
function createEnemyBullet(x, y) {
const size = 8;
const speed = 5 + gameState.speed / 50;
const angle = Math.atan2(
gameState.plane.y - y,
gameState.plane.x - x
);
gameState.enemyBullets.push({
x,
y,
size,
speed,
angle,
damage: 2
});
playSound('enemyShootSound');
}
// 创建障碍物
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 canShoot = Math.random() > 0.7;
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,
canShoot,
lastShootTime: 0,
shootCooldown: Math.random() * 2000 + 1000
});
}
// 创建Boss障碍物
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,
canShoot: true,
lastShootTime: 0,
shootCooldown: 500
});
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 distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
minDistance = distance;
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 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);
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 (timestamp - gameState.lastEnemyShootTime > 1000 / gameState.difficulty) {
gameState.obstacles.forEach(obstacle => {
if (obstacle.canShoot && timestamp - obstacle.lastShootTime > obstacle.shootCooldown) {
createEnemyBullet(obstacle.x - obstacle.width/2, obstacle.y);
obstacle.lastShootTime = timestamp;
}
});
gameState.lastEnemyShootTime = timestamp;
}
gameState.enemyBullets.forEach(bullet => {
bullet.x += Math.cos(bullet.angle) * bullet.speed * timeSlowFactor;
bullet.y += Math.sin(bullet.angle) * bullet.speed * timeSlowFactor;
});
gameState.enemyBullets = gameState.enemyBullets.filter(bullet =>
bullet.x > 0 && bullet.x < canvas.width &&
bullet.y > 0 && bullet.y < canvas.height
);
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(particle => {
particle.x += particle.speedX;
particle.y += particle.speedY;
particle.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(debris => {
debris.x += debris.speedX;
debris.y += debris.speedY;
debris.rotation += debris.rotationSpeed;
debris.opacity -= 0.02;
});
gameState.debris = gameState.debris.filter(debris => debris.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
});
}
}
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(missile =>
missile.x < canvas.width && missile.x > 0 &&
missile.y < canvas.height && missile.y > 0 &&
!missile.hit
);
gameState.clouds.forEach(cloud => {
cloud.x -= cloud.speed * timeSlowFactor;
});
gameState.clouds = gameState.clouds.filter(cloud => cloud.x + cloud.size > 0);
gameState.powerups.forEach(powerup => {
powerup.x -= powerup.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: powerup.x - powerup.size / 2,
y: powerup.y - powerup.size / 2,
width: powerup.size,
height: powerup.size
};
if (checkCollision(planeRect, powerupRect) && !gameState.gameOver) {
powerup.collected = true;
powerup.type.effect(gameState);
updateUI();
createParticles(powerup.x, powerup.y, 15, powerup.type.color);
}
});
gameState.powerups = gameState.powerups.filter(powerup =>
powerup.x + powerup.size > 0 && !powerup.collected
);
gameState.bullets.forEach(bullet => {
bullet.x += bullet.speed * timeSlowFactor;
});
gameState.bullets = gameState.bullets.filter(bullet => bullet.x < canvas.width);
gameState.enemyBullets.forEach(bullet => {
const bulletRect = {
x: bullet.x - bullet.size / 2,
y: bullet.y - bullet.size / 2,
width: bullet.size,
height: bullet.size
};
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
};
if (checkCollision(bulletRect, planeRect)) {
bullet.hit = true;
if (!gameState.plane.hasShield || gameState.plane.shieldDuration < Date.now()) {
gameState.lives--;
updateUI();
createExplosion(gameState.plane.x, gameState.plane.y);
if (gameState.lives <= 0) {
endGame();
}
} else {
createExplosion(bullet.x, bullet.y);
}
}
});
gameState.enemyBullets = gameState.enemyBullets.filter(bullet => !bullet.hit);
gameState.stars.forEach(star => {
star.x -= star.speed * timeSlowFactor;
star.rotation += star.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: star.x - star.size / 2,
y: star.y - star.size / 2,
width: star.size,
height: star.size
};
if (checkCollision(planeRect, starRect) && !gameState.gameOver) {
star.collected = true;
const scoreBonus = 10;
gameState.score += gameState.doubleScore > Date.now() ? scoreBonus * 2 : scoreBonus;
updateUI();
createParticles(star.x, star.y, 10, 'gold');
}
});
gameState.stars = gameState.stars.filter(star => star.x + star.size > 0 && !star.collected);
let comboCount = 0;
gameState.obstacles.forEach(obstacle => {
obstacle.x -= obstacle.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 obstacleRect = {
x: obstacle.x - obstacle.collisionWidth / 2,
y: obstacle.y - obstacle.collisionHeight / 2,
width: obstacle.collisionWidth,
height: obstacle.collisionHeight
};
if (checkCollision(planeRect, obstacleRect) && !gameState.gameOver) {
if (!gameState.plane.hasShield || gameState.plane.shieldDuration < Date.now()) {
obstacle.hit = true;
gameState.lives--;
updateUI();
createExplosion(gameState.plane.x, gameState.plane.y);
if (gameState.lives <= 0) {
endGame();
}
} else {
obstacle.hit = true;
createExplosion(obstacle.x, obstacle.y);
createDebris(obstacle, 4);
}
}
if (!obstacle.hit) {
const bulletHits = [];
gameState.bullets.forEach((bullet, bulletIndex) => {
const bulletRect = {
x: bullet.x - bullet.size / 2,
y: bullet.y - bullet.size / 2,
width: bullet.size,
height: bullet.size
};
const obstacleCollisionRect = {
x: obstacle.x - obstacle.collisionWidth / 2,
y: obstacle.y - obstacle.collisionHeight / 2,
width: obstacle.collisionWidth,
height: obstacle.collisionHeight
};
if (checkCollision(bulletRect, obstacleCollisionRect)) {
obstacle.health -= bullet.damage;
bulletHits.push(bulletIndex);
createExplosion(bullet.x, bullet.y);
if (obstacle.health <= 0) {
obstacle.hit = true;
const scoreBonus = obstacle.isBoss ? 500 : (obstacle.isLarge ? 30 : 15);
gameState.score += gameState.doubleScore > Date.now() ? scoreBonus * 2 : scoreBonus;
updateUI();
comboCount++;
if (obstacle.isLarge && !obstacle.isBoss) {
for (let i = 0; i < 3; i++) {
gameState.obstacles.push({
x: obstacle.x + (Math.random() - 0.5) * 50,
y: obstacle.y + (Math.random() - 0.5) * 50,
width: obstacle.width / 2,
height: obstacle.height / 2,
collisionWidth: obstacle.collisionWidth / 2,
collisionHeight: obstacle.collisionHeight / 2,
speed: obstacle.speed * 1.2,
type: obstacle.type,
health: 1,
maxHealth: 1,
isLarge: false
});
}
}
if (obstacle.isBoss) {
gameState.bossActive = false;
gameState.achievements.bossSlayer = true;
}
createDebris(obstacle, obstacle.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(obstacle => obstacle.x + obstacle.width > 0 && !obstacle.hit);
gameState.explosions.forEach(explosion => {
explosion.size += 2;
explosion.alpha -= 0.02;
});
gameState.explosions = gameState.explosions.filter(explosion => explosion.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(particle => {
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.globalAlpha = particle.life / 100;
ctx.fill();
ctx.globalAlpha = 1;
});
gameState.debris.forEach(debris => {
ctx.save();
ctx.translate(debris.x, debris.y);
ctx.rotate(debris.rotation);
ctx.fillStyle = `rgba(100, 100, 100, ${debris.opacity})`;
ctx.fillRect(
-debris.width / 2,
-debris.height / 2,
debris.width,
debris.height
);
ctx.restore();
});
gameState.enemyBullets.forEach(bullet => {
const gradient = ctx.createRadialGradient(
bullet.x, bullet.y, 0,
bullet.x, bullet.y, bullet.size
);
gradient.addColorStop(0, '#f00');
gradient.addColorStop(1, '#800');
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.fill();
ctx.beginPath();
ctx.moveTo(bullet.x - Math.cos(bullet.angle) * 10, bullet.y - Math.sin(bullet.angle) * 10);
ctx.lineTo(bullet.x, bullet.y);
ctx.strokeStyle = 'rgba(255, 100, 100, 0.8)';
ctx.lineWidth = bullet.size / 2;
ctx.stroke();
});
gameState.powerups.forEach(powerup => {
ctx.save();
ctx.translate(powerup.x, powerup.y);
ctx.beginPath();
ctx.arc(0, 0, powerup.size / 2, 0, Math.PI * 2);
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, powerup.size / 2);
gradient.addColorStop(0, powerup.type.color);
gradient.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = gradient;
ctx.globalAlpha = 0.3;
ctx.fill();
ctx.globalAlpha = 1;
ctx.fillStyle = powerup.type.color;
ctx.beginPath();
ctx.arc(0, 0, powerup.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(powerup.type.icon), 16)), 0, 1);
ctx.restore();
});
gameState.homingMissiles.forEach(missile => {
ctx.save();
ctx.translate(missile.x, missile.y);
ctx.rotate(missile.angle);
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.moveTo(missile.size / 2, 0);
ctx.lineTo(-missile.size / 2, -missile.size / 3);
ctx.lineTo(-missile.size / 2, missile.size / 3);
ctx.closePath();
ctx.fill();
ctx.fillStyle = 'orange';
ctx.beginPath();
ctx.moveTo(-missile.size / 2, -missile.size / 4);
ctx.lineTo(-missile.size, 0);
ctx.lineTo(-missile.size / 2, missile.size / 4);
ctx.closePath();
ctx.fill();
ctx.restore();
});
gameState.bullets.forEach(bullet => {
const gradient = ctx.createRadialGradient(
bullet.x, bullet.y, 0,
bullet.x, bullet.y, bullet.size
);
gradient.addColorStop(0, '#ff0');
gradient.addColorStop(1, '#f80');
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.fill();
ctx.beginPath();
ctx.moveTo(bullet.x - bullet.speed, bullet.y);
ctx.lineTo(bullet.x, bullet.y);
ctx.strokeStyle = 'rgba(255, 200, 0, 0.8)';
ctx.lineWidth = bullet.size / 2;
ctx.stroke();
});
gameState.stars.forEach(star => {
ctx.save();
ctx.translate(star.x, star.y);
ctx.rotate(star.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 = star.size / 2;
const innerRadius = star.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 gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, star.size / 2);
gradient.addColorStop(0, 'gold');
gradient.addColorStop(1, 'yellow');
ctx.fillStyle = gradient;
ctx.shadowColor = 'yellow';
ctx.shadowBlur = 10;
ctx.fill();
ctx.restore();
});
// 绘制障碍物
gameState.obstacles.forEach(obstacle => {
ctx.save();
ctx.translate(obstacle.x, obstacle.y);
if (obstacle.type === 'rectangle') {
if (obstacle.health < obstacle.maxHealth) {
const healthBarWidth = obstacle.isBoss ? 100 : 20;
ctx.fillStyle = 'red';
ctx.fillRect(
-healthBarWidth / 2,
-obstacle.height / 2 - 15,
healthBarWidth,
5
);
ctx.fillStyle = obstacle.isBoss ? 'purple' : 'lime';
ctx.fillRect(
-healthBarWidth / 2,
-obstacle.height / 2 - 15,
healthBarWidth * (obstacle.health / obstacle.maxHealth),
5
);
}
if (obstacle.canShoot) {
let bodyGradient = ctx.createLinearGradient(-obstacle.width/2, 0, obstacle.width/2, 0);
if (obstacle.isBoss) {
bodyGradient.addColorStop(0, '#ff4d4d');
bodyGradient.addColorStop(1, '#8B0000');
} else {
bodyGradient.addColorStop(0, '#666');
bodyGradient.addColorStop(1, '#333');
}
ctx.fillStyle = bodyGradient;
ctx.shadowColor = 'rgba(0,0,0,0.4)';
ctx.shadowBlur = 4;
ctx.beginPath();
ctx.moveTo(-obstacle.width/2, 0);
ctx.lineTo(obstacle.width/2, -obstacle.height/3);
ctx.lineTo(obstacle.width/2, obstacle.height/3);
ctx.closePath();
ctx.fill();
ctx.shadowBlur = 0;
let wingGradient = ctx.createLinearGradient(-obstacle.width/4, 0, obstacle.width/4, 0);
wingGradient.addColorStop(0, obstacle.isBoss ? '#a83232' : '#444');
wingGradient.addColorStop(1, obstacle.isBoss ? '#600000' : '#222');
ctx.fillStyle = wingGradient;
ctx.beginPath();
ctx.moveTo(-obstacle.width/4, 0);
ctx.lineTo(obstacle.width/4, -obstacle.height/2);
ctx.lineTo(obstacle.width/2, -obstacle.height/3);
ctx.lineTo(obstacle.width/4, 0);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(-obstacle.width/4, 0);
ctx.lineTo(obstacle.width/4, obstacle.height/2);
ctx.lineTo(obstacle.width/2, obstacle.height/3);
ctx.lineTo(obstacle.width/4, 0);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(-obstacle.width/2, 0);
ctx.lineTo(-obstacle.width/3, -obstacle.height/4);
ctx.lineTo(-obstacle.width/4, -obstacle.height/4);
ctx.lineTo(-obstacle.width/3, 0);
ctx.closePath();
ctx.fillStyle = obstacle.isBoss ? '#800000' : '#555';
ctx.fill();
ctx.beginPath();
ctx.moveTo(-obstacle.width/2, 0);
ctx.lineTo(-obstacle.width/3, obstacle.height/4);
ctx.lineTo(-obstacle.width/4, obstacle.height/4);
ctx.lineTo(-obstacle.width/3, 0);
ctx.closePath();
ctx.fill();
let cockpitGradient = ctx.createRadialGradient(obstacle.width/4, 0, 0, obstacle.width/4, 0, obstacle.width/8);
cockpitGradient.addColorStop(0, '#80dfff');
cockpitGradient.addColorStop(1, '#3498db');
ctx.fillStyle = cockpitGradient;
ctx.beginPath();
ctx.arc(obstacle.width/4, 0, obstacle.width/8, 0, Math.PI * 2);
ctx.fill();
if (obstacle.isBoss) {
ctx.fillStyle = 'gold';
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.fillText('BOSS', 0, 0);
}
} else {
ctx.fillStyle = obstacle.isBoss ? '#8B0000' : (obstacle.isLarge ? '#333' : '#555');
ctx.fillRect(
-obstacle.width / 2,
-obstacle.height / 2,
obstacle.width,
obstacle.height
);
ctx.fillStyle = obstacle.isBoss ? '#600000' : (obstacle.isLarge ? '#222' : '#444');
ctx.fillRect(
-obstacle.width / 2 + 5,
-obstacle.height / 2 + 5,
obstacle.width - 10,
obstacle.height - 10
);
if ((obstacle.isLarge || obstacle.isBoss) && obstacle.health < obstacle.maxHealth) {
ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
ctx.lineWidth = 2;
for (let i = 0; i < (obstacle.isBoss ? 10 : 3); i++) {
ctx.beginPath();
ctx.moveTo(
-obstacle.width / 2 + Math.random() * obstacle.width,
-obstacle.height / 2 + Math.random() * obstacle.height / 3
);
ctx.lineTo(
-obstacle.width / 2 + Math.random() * obstacle.width,
obstacle.height / 2 - Math.random() * obstacle.height / 3
);
ctx.stroke();
}
}
if (obstacle.isBoss) {
ctx.fillStyle = 'gold';
ctx.font = 'bold 20px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('BOSS', 0, 0);
}
}
} else {
// 如果障碍物类型为"circle",我们将其视为陨石并绘制贴图
if (meteorImage.complete) {
ctx.drawImage(meteorImage, -obstacle.width / 2, -obstacle.height / 2, obstacle.width, obstacle.height);
} else {
// 贴图未加载完成时绘制占位圆
ctx.beginPath();
ctx.arc(0, 0, obstacle.width / 2, 0, Math.PI * 2);
ctx.fillStyle = '#555';
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 gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 45);
gradient.addColorStop(0, 'rgba(0, 204, 255, 0.2)');
gradient.addColorStop(1, 'rgba(0, 204, 255, 0)');
ctx.fillStyle = gradient;
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(explosion => {
ctx.save();
ctx.translate(explosion.x, explosion.y);
const gradient = ctx.createRadialGradient(
0, 0, 0,
0, 0, explosion.size
);
gradient.addColorStop(0, `rgba(255, 100, 0, ${explosion.alpha})`);
gradient.addColorStop(0.5, `rgba(255, 200, 0, ${explosion.alpha * 0.6})`);
gradient.addColorStop(1, `rgba(255, 255, 255, 0)`);
ctx.beginPath();
ctx.arc(0, 0, explosion.size, 0, Math.PI * 2);
ctx.fillStyle = gradient;
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();
}
}
}
// 辅助函数: 获取FontAwesome图标的Unicode
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;
let startY = 0;
let handleX = 0;
let 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 distance = Math.sqrt(handleX * handleX + handleY * handleY);
if (distance > maxDistance) {
handleX = (handleX / distance) * maxDistance;
handleY = (handleY / distance) * maxDistance;
}
handle.style.transform = `translate(${handleX}px, ${handleY}px)`;
gameState.joystickAngle = Math.atan2(handleY, handleX);
gameState.joystickDistance = distance / 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 distance = Math.sqrt(handleX * handleX + handleY * handleY);
if (distance > maxDistance) {
handleX = (handleX / distance) * maxDistance;
handleY = (handleY / distance) * maxDistance;
}
handle.style.transform = `translate(${handleX}px, ${handleY}px)`;
gameState.joystickAngle = Math.atan2(handleY, handleX);
gameState.joystickDistance = distance / 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>