class Game { constructor() { this.canvas = document.getElementById('gameCanvas'); this.ctx = this.canvas.getContext('2d'); this.setupVerticalScreen(); this.canvas.width = this.width; this.canvas.height = this.height; this.player = null; this.fishes = []; this.particles = []; this.score = 0; this.level = 1; this.coins = 0; this.gameState = 'start'; this.channel = 1; this.channelScore = 0; this.targetX = this.width / 2; this.targetY = this.height / 2; this.joystickActive = false; this.joystickAngle = 0; this.creatureTypes = [ { name: '蒲公英', color: '#FFE4E1', size: 12, speed: 2.5, glowColor: 'rgba(255, 228, 225, 0.6)' }, { name: '蝴蝶', color: '#FFB6C1', size: 20, speed: 2.2, glowColor: 'rgba(255, 182, 193, 0.6)' }, { name: '小鸟', color: '#87CEEB', size: 28, speed: 1.8, glowColor: 'rgba(135, 206, 235, 0.6)' }, { name: '蜜蜂', color: '#FFD700', size: 38, speed: 1.5, glowColor: 'rgba(255, 215, 0, 0.6)' }, { name: '蝴蝶群', color: '#FF69B4', size: 50, speed: 1.2, glowColor: 'rgba(255, 105, 180, 0.6)' }, { name: '老鹰', color: '#4682B4', size: 65, speed: 1.0, glowColor: 'rgba(70, 130, 180, 0.6)' }, { name: '凤凰', color: '#FF4500', size: 85, speed: 0.8, glowColor: 'rgba(255, 69, 0, 0.7)' }, { name: '神鸟', color: '#FF1493', size: 110, speed: 0.6, glowColor: 'rgba(255, 20, 147, 0.8)' } ]; this.init(); } init() { window.addEventListener('resize', () => this.resize()); if (typeof checkDailyReset === 'function') { checkDailyReset(); } this.setupJoystick(); this.createPlayer(); this.spawnInitialCreatures(); this.createOceanBubbles(); this.gameLoop(); } setupJoystick() { const joystickContainer = document.getElementById('joystick-container'); const joystickOuterRing = document.getElementById('joystick-outer-ring'); const joystickHandle = document.getElementById('joystick-handle'); if (!joystickContainer || !joystickOuterRing || !joystickHandle) return; const baseRect = joystickOuterRing.getBoundingClientRect(); const baseCenterX = baseRect.width / 2; const baseCenterY = baseRect.height / 2; const handleRadius = 65 / 2; const maxDistance = 50; let isDragging = false; let dragStartX = 0; let dragStartY = 0; let containerStartX = 0; let containerStartY = 0; const handleMove = (clientX, clientY) => { if (this.gameState !== 'playing') return; if (isDragging) return; const rect = joystickOuterRing.getBoundingClientRect(); const x = clientX - rect.left - baseCenterX; const y = clientY - rect.top - baseCenterY; const distance = Math.sqrt(x * x + y * y); if (distance < 5) { this.joystickActive = false; joystickHandle.style.transform = 'translate(-50%, -50%)'; return; } this.joystickActive = true; this.joystickAngle = Math.atan2(y, x); const normalizedX = (x / distance) * Math.min(distance, maxDistance); const normalizedY = (y / distance) * Math.min(distance, maxDistance); joystickHandle.style.transform = `translate(calc(-50% + ${normalizedX}px), calc(-50% + ${normalizedY}px))`; this.targetX = this.player.x + Math.cos(this.joystickAngle) * 400; this.targetY = this.player.y + Math.sin(this.joystickAngle) * 400; }; const handleEnd = () => { this.joystickActive = false; isDragging = false; joystickHandle.style.transform = 'translate(-50%, -50%)'; }; const startDrag = (e) => { const rect = joystickContainer.getBoundingClientRect(); dragStartX = e.clientX; dragStartY = e.clientY; containerStartX = rect.left; containerStartY = rect.top; isDragging = true; this.joystickActive = false; joystickHandle.style.transform = 'translate(-50%, -50%)'; }; const handleDrag = (e) => { if (!isDragging) return; const deltaX = e.clientX - dragStartX; const deltaY = e.clientY - dragStartY; let newX = containerStartX + deltaX; let newY = containerStartY + deltaY; const containerWidth = joystickContainer.offsetWidth; const containerHeight = joystickContainer.offsetHeight; newX = Math.max(0, Math.min(window.innerWidth - containerWidth, newX)); newY = Math.max(0, Math.min(window.innerHeight - containerHeight, newY)); joystickContainer.style.left = `${newX}px`; joystickContainer.style.bottom = 'auto'; joystickContainer.style.top = `${newY}px`; joystickContainer.style.transform = 'none'; }; joystickHandle.addEventListener('mousedown', (e) => { e.preventDefault(); handleMove(e.clientX, e.clientY); }); joystickOuterRing.addEventListener('mousedown', (e) => { e.preventDefault(); handleMove(e.clientX, e.clientY); }); joystickContainer.addEventListener('touchstart', (e) => { if (e.touches.length === 2) { e.preventDefault(); const touch = e.touches[0]; startDrag({ clientX: touch.clientX, clientY: touch.clientY }); } }); joystickHandle.addEventListener('touchstart', (e) => { if (e.touches.length === 1) { e.preventDefault(); const touch = e.touches[0]; handleMove(touch.clientX, touch.clientY); } }); joystickOuterRing.addEventListener('touchstart', (e) => { if (e.touches.length === 1) { e.preventDefault(); const touch = e.touches[0]; handleMove(touch.clientX, touch.clientY); } }); document.addEventListener('mousemove', (e) => { if (isDragging) { handleDrag(e); } else if (this.joystickActive) { handleMove(e.clientX, e.clientY); } }); document.addEventListener('touchmove', (e) => { if (e.touches.length === 1 && this.joystickActive && !isDragging) { const touch = e.touches[0]; handleMove(touch.clientX, touch.clientY); } else if (e.touches.length >= 2 && isDragging) { const touch = e.touches[0]; handleDrag({ clientX: touch.clientX, clientY: touch.clientY }); } }); document.addEventListener('mouseup', handleEnd); document.addEventListener('touchend', (e) => { if (e.touches.length < 2) { handleEnd(); } }); } setupVerticalScreen() { const aspectRatio = 9 / 16; const windowHeight = window.innerHeight; const windowWidth = window.innerWidth; const availableHeight = windowHeight - 80; if (windowWidth / availableHeight > aspectRatio) { this.height = availableHeight; this.width = this.height * aspectRatio; } else { this.width = windowWidth; this.height = this.width / aspectRatio; } this.canvas.style.width = `${this.width}px`; this.canvas.style.height = `${this.height}px`; } createOceanBubbles() { const container = document.getElementById('oceanParticles'); if (!container) return; for (let i = 0; i < 15; i++) { const bubble = document.createElement('div'); bubble.className = 'bubble'; bubble.style.left = `${Math.random() * 100}%`; bubble.style.animationDelay = `${Math.random() * 8}s`; bubble.style.animationDuration = `${6 + Math.random() * 4}s`; bubble.style.width = `${2 + Math.random() * 4}px`; bubble.style.height = bubble.style.width; container.appendChild(bubble); } } resize() { this.setupVerticalScreen(); this.canvas.width = this.width; this.canvas.height = this.height; } createPlayer() { let initialTypeIndex = 0; if (this.channel === 1) { initialTypeIndex = 5; } else if (this.channel === 2) { initialTypeIndex = 4; } else if (this.channel === 3) { initialTypeIndex = 3; } else if (this.channel === 4) { initialTypeIndex = 2; } else if (this.channel === 5) { initialTypeIndex = 1; } const type = this.creatureTypes[initialTypeIndex]; this.player = { x: this.width / 2, y: this.height / 2, size: type.size, color: '#00FFFF', speed: type.speed * 0.6 + 0.8, typeIndex: initialTypeIndex, trail: [], invincible: this.channel <= 2, glowColor: 'rgba(0, 255, 255, 0.8)', isPlayer: true, rainbowPhase: 0, angle: 0 }; } spawnInitialCreatures() { this.fishes = []; const count = this.getCreatureCount(); for (let i = 0; i < count; i++) { this.spawnCreature(); } } getCreatureCount() { if (this.channel <= 10) { return 4 + this.channel * 0.5; } else if (this.channel <= 20) { return 8 + this.channel * 0.8; } else { return 12 + this.channel; } } getMaxCreatureType() { if (this.channel <= 10) { return Math.min(1, this.creatureTypes.length - 1); } else if (this.channel <= 20) { return Math.min(2, this.creatureTypes.length - 1); } else if (this.channel <= 30) { return Math.min(3, this.creatureTypes.length - 1); } else if (this.channel <= 40) { return Math.min(4, this.creatureTypes.length - 1); } else { return Math.min(5, this.creatureTypes.length - 1); } } getCreatureSpeedMultiplier() { if (this.channel <= 20) { return 0.3 + this.channel * 0.02; } else if (this.channel <= 40) { return 0.7 + (this.channel - 20) * 0.02; } else { return 1.1 + (this.channel - 40) * 0.02; } } spawnCreature(typeIndex = null) { const maxType = this.getMaxCreatureType(); const idx = typeIndex !== null ? typeIndex : Math.floor(Math.random() * (maxType + 1)); const creatureType = this.creatureTypes[idx]; const speedMultiplier = this.getCreatureSpeedMultiplier(); const fish = { x: Math.random() * this.width, y: Math.random() * this.height, size: creatureType.size, color: creatureType.color, speed: (creatureType.speed + Math.random() * 0.4) * speedMultiplier, typeIndex: idx, angle: Math.random() * Math.PI * 2, wanderAngle: Math.random() * Math.PI * 2, glowColor: creatureType.glowColor, pulsePhase: Math.random() * Math.PI * 2 }; this.fishes.push(fish); } updatePlayer() { const dx = this.targetX - this.player.x; const dy = this.targetY - this.player.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist > 8) { const moveSpeed = this.player.speed * (0.5 + dist / 400); this.player.x += (dx / dist) * moveSpeed; this.player.y += (dy / dist) * moveSpeed; this.player.angle = Math.atan2(dy, dx); } this.player.x = Math.max(this.player.size, Math.min(this.width - this.player.size, this.player.x)); this.player.y = Math.max(this.player.size, Math.min(this.height - this.player.size, this.player.y)); this.player.trail.push({ x: this.player.x, y: this.player.y, size: this.player.size, color: this.player.color, angle: this.player.angle }); if (this.player.trail.length > 12) { this.player.trail.shift(); } } updateCreatures() { for (let fish of this.fishes) { fish.wanderAngle += (Math.random() - 0.5) * 0.12; fish.angle += Math.sin(fish.wanderAngle) * 0.025; fish.pulsePhase += 0.05; fish.x += Math.cos(fish.angle) * fish.speed; fish.y += Math.sin(fish.angle) * fish.speed; if (fish.x < fish.size || fish.x > this.width - fish.size) { fish.angle = Math.PI - fish.angle; } if (fish.y < fish.size || fish.y > this.height - fish.size) { fish.angle = -fish.angle; } fish.x = Math.max(fish.size, Math.min(this.width - fish.size, fish.x)); fish.y = Math.max(fish.size, Math.min(this.height - fish.size, fish.y)); } } checkCollisions() { for (let i = this.fishes.length - 1; i >= 0; i--) { const fish = this.fishes[i]; const dx = this.player.x - fish.x; const dy = this.player.y - fish.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < this.player.size * 0.8 + fish.size * 0.8) { if (this.player.typeIndex > fish.typeIndex) { this.eatCreature(fish, i); } else if (this.player.typeIndex < fish.typeIndex) { if (!this.player.invincible) { this.gameOver(); } } } } } eatCreature(fish, index) { const points = (fish.typeIndex + 1) * 15; this.score += points; this.channelScore += points; if (Math.random() < 0.3) { this.coins += 1; } this.fishes.splice(index, 1); this.createParticles(fish.x, fish.y, fish.color, fish.size); if (this.player.typeIndex < this.creatureTypes.length - 1 && this.score >= (this.player.typeIndex + 1) * 120) { this.upgradePlayer(); } const channelThreshold = this.getChannelThreshold(); if (this.channelScore >= channelThreshold) { this.channelUp(); } const minCreatures = this.getCreatureCount() * 0.6; if (this.fishes.length < minCreatures) { this.spawnCreature(); } this.updateUI(); } getChannelThreshold() { if (this.channel <= 5) { return 300 + this.channel * 100; } else if (this.channel <= 10) { return 800 + (this.channel - 5) * 200; } else { return 1800 + (this.channel - 10) * 300; } } channelUp() { this.channel++; this.channelScore = 0; this.createParticles(this.width / 2, this.height / 2, '#FFD700', 50, true); this.spawnInitialCreatures(); this.updateUI(); } upgradePlayer() { this.player.typeIndex++; const newType = this.creatureTypes[this.player.typeIndex]; this.player.size = newType.size; this.player.color = this.getRainbowColor(); this.player.speed = Math.max(0.8, this.player.speed - 0.08); this.player.glowColor = this.getRainbowGlow(); this.createParticles(this.player.x, this.player.y, '#00ffcc', this.player.size, true); if (this.player.typeIndex >= this.creatureTypes.length - 1) { this.levelUp(); } } getRainbowColor() { const hue = (Date.now() / 50) % 360; return `hsl(${hue}, 100%, 65%)`; } getRainbowGlow() { const hue = (Date.now() / 50) % 360; return `hsla(${hue}, 100%, 60%, 0.8)`; } levelUp() { this.level++; this.player.invincible = true; setTimeout(() => { this.player.invincible = false; }, 4000); for (let i = 0; i < 6; i++) { setTimeout(() => { this.spawnCreature(Math.floor(Math.random() * this.creatureTypes.length)); }, i * 180); } this.updateUI(); } createParticles(x, y, color, size, big = false) { const count = big ? 40 : 12; for (let i = 0; i < count; i++) { this.particles.push({ x: x, y: y, vx: (Math.random() - 0.5) * (big ? 12 : 8), vy: (Math.random() - 0.5) * (big ? 12 : 8), size: (big ? Math.random() * 15 + 8 : Math.random() * 6 + 3), color: color, life: 1, gravity: 0.1 }); } } updateParticles() { for (let i = this.particles.length - 1; i >= 0; i--) { const p = this.particles[i]; p.x += p.vx; p.y += p.vy; p.vy += p.gravity; p.vx *= 0.98; p.vy *= 0.98; p.life -= 0.018; if (p.life <= 0) { this.particles.splice(i, 1); } } } addCoins(amount) { this.coins += amount; this.updateUI(); if (typeof saveDailyCoins === 'function') { saveDailyCoins(); } } updateUI() { document.getElementById('score').innerHTML = `分数: ${this.score}`; document.getElementById('level').innerHTML = `等级: ${this.level}`; document.getElementById('channel').innerHTML = `通道: ${this.channel}`; document.getElementById('coins').innerHTML = `金币: ${this.coins}`; } draw() { this.ctx.clearRect(0, 0, this.width, this.height); this.drawBackground(); for (let p of this.particles) { this.ctx.globalAlpha = p.life * 0.8; this.ctx.fillStyle = p.color; this.ctx.beginPath(); this.ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2); this.ctx.fill(); } this.ctx.globalAlpha = 1; for (let fish of this.fishes) { this.drawCreature(fish.x, fish.y, fish.size, fish.color, fish.angle, fish.glowColor, fish.pulsePhase); } for (let i = 0; i < this.player.trail.length; i++) { const t = this.player.trail[i]; const alpha = (i + 1) / this.player.trail.length * 0.4; const scale = 0.6 + (i + 1) / this.player.trail.length * 0.4; this.ctx.globalAlpha = alpha; this.drawCreature(t.x, t.y, t.size * scale, t.color, t.angle || 0, this.player.glowColor, 0); } this.ctx.globalAlpha = 1; this.drawCreature(this.player.x, this.player.y, this.player.size, this.player.color, this.player.angle, this.player.glowColor, 0, true); if (this.player.invincible) { this.ctx.strokeStyle = 'rgba(255, 255, 0, 0.6)'; this.ctx.lineWidth = 4; this.ctx.setLineDash([10, 5]); this.ctx.beginPath(); this.ctx.arc(this.player.x, this.player.y, this.player.size + 15, 0, Math.PI * 2); this.ctx.stroke(); this.ctx.setLineDash([]); } } drawBackground() { const gradient = this.ctx.createLinearGradient(0, 0, 0, this.height); gradient.addColorStop(0, '#87CEEB'); gradient.addColorStop(0.3, '#B0E0E6'); gradient.addColorStop(0.6, '#E0F4FF'); gradient.addColorStop(1, '#F0F8FF'); this.ctx.fillStyle = gradient; this.ctx.fillRect(0, 0, this.width, this.height); this.drawClouds(); this.drawFloatingFish(); this.drawBubbles(); } drawClouds() { const clouds = [ { x: 50, y: 80, scale: 1.2, opacity: 0.9 }, { x: 200, y: 150, scale: 0.8, opacity: 0.8 }, { x: 350, y: 60, scale: 1.0, opacity: 0.7 }, { x: 150, y: 300, scale: 0.9, opacity: 0.85 }, { x: 400, y: 250, scale: 1.1, opacity: 0.75 }, { x: 80, y: 380, scale: 0.7, opacity: 0.8 }, { x: 300, y: 420, scale: 1.3, opacity: 0.7 }, ]; const time = Date.now() / 5000; clouds.forEach((cloud, i) => { const offsetX = Math.sin(time + i * 0.5) * 10; const x = ((cloud.x + offsetX) % (this.width + 200)) - 100; const y = cloud.y; this.ctx.save(); this.ctx.translate(x, y); this.ctx.scale(cloud.scale, cloud.scale); this.ctx.globalAlpha = cloud.opacity; this.ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; this.ctx.beginPath(); this.ctx.arc(0, 0, 30, 0, Math.PI * 2); this.ctx.arc(30, -10, 25, 0, Math.PI * 2); this.ctx.arc(60, 0, 30, 0, Math.PI * 2); this.ctx.arc(30, 10, 25, 0, Math.PI * 2); this.ctx.fill(); this.ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; this.ctx.beginPath(); this.ctx.arc(15, -5, 15, 0, Math.PI * 2); this.ctx.arc(45, -5, 15, 0, Math.PI * 2); this.ctx.fill(); this.ctx.restore(); }); } drawFloatingFish() { const fishColors = ['#FF9AA2', '#FFB7B2', '#FFDAC1', '#E2F0CB', '#B5EAD7', '#C7CEEA', '#F8B195', '#F67280']; const time = Date.now() / 3000; for (let i = 0; i < 15; i++) { const baseX = (i * 157) % this.width; const baseY = (i * 83) % this.height; const x = baseX + Math.sin(time + i * 0.7) * 40; const y = baseY + Math.cos(time * 0.7 + i * 0.5) * 25; const size = 6 + (i % 4) * 3; const color = fishColors[i % fishColors.length]; const direction = (i % 2) === 0 ? 1 : -1; this.ctx.save(); this.ctx.translate(x, y); this.ctx.scale(direction, 1); this.ctx.globalAlpha = 0.4 + Math.sin(time + i) * 0.15; const gradient = this.ctx.createRadialGradient(-size * 0.2, -size * 0.2, 0, 0, 0, size); gradient.addColorStop(0, this.lightenColor(color, 30)); gradient.addColorStop(0.6, color); gradient.addColorStop(1, this.darkenColor(color, 20)); this.ctx.fillStyle = gradient; this.ctx.beginPath(); this.ctx.moveTo(size * 0.8, 0); this.ctx.quadraticCurveTo(size * 0.4, -size * 0.5, -size * 0.2, -size * 0.3); this.ctx.quadraticCurveTo(-size * 0.4, 0, -size * 0.2, size * 0.3); this.ctx.quadraticCurveTo(size * 0.4, size * 0.5, size * 0.8, 0); this.ctx.fill(); this.ctx.fillStyle = this.darkenColor(color, 10); this.ctx.beginPath(); this.ctx.moveTo(-size * 0.2, 0); this.ctx.quadraticCurveTo(-size * 0.5, -size * 0.3, -size * 0.7, -size * 0.2); this.ctx.quadraticCurveTo(-size * 0.55, 0, -size * 0.7, size * 0.2); this.ctx.quadraticCurveTo(-size * 0.5, size * 0.3, -size * 0.2, 0); this.ctx.fill(); this.ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; this.ctx.beginPath(); this.ctx.ellipse(size * 0.25, -size * 0.1, size * 0.15, size * 0.1, 0, 0, Math.PI * 2); this.ctx.fill(); this.ctx.fillStyle = '#2d3436'; this.ctx.beginPath(); this.ctx.arc(size * 0.28, -size * 0.1, size * 0.06, 0, Math.PI * 2); this.ctx.fill(); this.ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; this.ctx.beginPath(); this.ctx.arc(size * 0.3, -size * 0.12, size * 0.025, 0, Math.PI * 2); this.ctx.fill(); this.ctx.fillStyle = '#ff6b9d'; this.ctx.beginPath(); this.ctx.ellipse(size * 0.6, size * 0.05, size * 0.06, size * 0.03, 0, 0, Math.PI * 2); this.ctx.fill(); this.ctx.restore(); } } drawBubbles() { const time = Date.now() / 2000; for (let i = 0; i < 30; i++) { const x = ((i * 137.5 + time * 50) % (this.width + 40)) - 20; const y = this.height + ((i * 73.3 + time * 30) % (this.height + 100)) - 50; const size = 2 + (i % 5); const opacity = 0.3 + Math.sin(time + i) * 0.2; this.ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`; this.ctx.beginPath(); this.ctx.arc(x, y, size, 0, Math.PI * 2); this.ctx.fill(); this.ctx.fillStyle = `rgba(255, 255, 255, ${opacity * 0.6})`; this.ctx.beginPath(); this.ctx.arc(x - size * 0.3, y - size * 0.3, size * 0.3, 0, Math.PI * 2); this.ctx.fill(); } } drawCreature(x, y, size, color, angle, glowColor, pulsePhase, isPlayer = false) { this.ctx.save(); this.ctx.translate(x, y); this.ctx.rotate(angle); const pulseScale = 1 + Math.sin(pulsePhase) * 0.08; this.ctx.scale(pulseScale, pulseScale); if (isPlayer) { this.ctx.shadowColor = 'rgba(0, 255, 255, 0.8)'; this.ctx.shadowBlur = size * 0.8; } else if (glowColor) { this.ctx.shadowColor = glowColor; this.ctx.shadowBlur = size * 0.4; } const gradient = this.ctx.createRadialGradient(-size * 0.2, -size * 0.2, 0, 0, 0, size); gradient.addColorStop(0, this.lightenColor(color, 40)); gradient.addColorStop(0.5, color); gradient.addColorStop(0.85, this.darkenColor(color, 20)); gradient.addColorStop(1, this.darkenColor(color, 40)); this.ctx.fillStyle = gradient; this.ctx.beginPath(); this.ctx.moveTo(size * 0.9, 0); this.ctx.quadraticCurveTo(size * 0.5, -size * 0.6, -size * 0.3, -size * 0.35); this.ctx.quadraticCurveTo(-size * 0.5, 0, -size * 0.3, size * 0.35); this.ctx.quadraticCurveTo(size * 0.5, size * 0.6, size * 0.9, 0); this.ctx.fill(); const tailGradient = this.ctx.createLinearGradient(-size * 0.3, 0, -size * 0.9, 0); tailGradient.addColorStop(0, color); tailGradient.addColorStop(1, this.darkenColor(color, 30)); this.ctx.fillStyle = tailGradient; this.ctx.beginPath(); this.ctx.moveTo(-size * 0.3, 0); this.ctx.quadraticCurveTo(-size * 0.6, -size * 0.4, -size * 0.9, -size * 0.25); this.ctx.quadraticCurveTo(-size * 0.7, 0, -size * 0.9, size * 0.25); this.ctx.quadraticCurveTo(-size * 0.6, size * 0.4, -size * 0.3, 0); this.ctx.fill(); this.ctx.shadowBlur = 0; const finGradient = this.ctx.createLinearGradient(0, -size * 0.5, 0, -size * 0.8); finGradient.addColorStop(0, color); finGradient.addColorStop(1, this.darkenColor(color, 20)); this.ctx.fillStyle = finGradient; this.ctx.beginPath(); this.ctx.moveTo(size * 0.1, -size * 0.4); this.ctx.quadraticCurveTo(-size * 0.1, -size * 0.7, size * 0.3, -size * 0.5); this.ctx.quadraticCurveTo(size * 0.2, -size * 0.4, size * 0.1, -size * 0.4); this.ctx.fill(); this.ctx.fillStyle = 'rgba(255, 255, 255, 0.95)'; this.ctx.beginPath(); this.ctx.ellipse(size * 0.35, -size * 0.12, size * 0.22, size * 0.16, 0, 0, Math.PI * 2); this.ctx.fill(); this.ctx.fillStyle = '#1a1a2e'; this.ctx.beginPath(); this.ctx.arc(size * 0.4, -size * 0.12, size * 0.11, 0, Math.PI * 2); this.ctx.fill(); this.ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; this.ctx.beginPath(); this.ctx.arc(size * 0.43, -size * 0.15, size * 0.04, 0, Math.PI * 2); this.ctx.fill(); this.ctx.fillStyle = '#ff6b9d'; this.ctx.beginPath(); this.ctx.ellipse(size * 0.75, size * 0.08, size * 0.08, size * 0.04, 0, 0, Math.PI * 2); this.ctx.fill(); this.ctx.strokeStyle = this.darkenColor(color, 30); this.ctx.lineWidth = 1.5; this.ctx.lineCap = 'round'; this.ctx.beginPath(); this.ctx.moveTo(size * 0.72, size * 0.12); this.ctx.quadraticCurveTo(size * 0.68, size * 0.18, size * 0.75, size * 0.15); this.ctx.stroke(); if (isPlayer) { this.ctx.strokeStyle = 'rgba(0, 255, 255, 0.8)'; this.ctx.lineWidth = 3; this.ctx.shadowColor = 'rgba(0, 255, 255, 0.6)'; this.ctx.shadowBlur = 10; this.ctx.beginPath(); this.ctx.arc(0, 0, size * 1.3, 0, Math.PI * 2); this.ctx.stroke(); this.ctx.shadowBlur = 0; this.ctx.strokeStyle = 'rgba(255, 215, 0, 0.6)'; this.ctx.lineWidth = 2; this.ctx.beginPath(); this.ctx.arc(0, 0, size * 1.5, -Math.PI / 6, Math.PI / 6); this.ctx.stroke(); } this.ctx.restore(); } lightenColor(color, percent) { const num = parseInt(color.replace('#', ''), 16); const amt = Math.round(2.55 * percent); const R = Math.min(255, (num >> 16) + amt); const G = Math.min(255, ((num >> 8) & 0x00FF) + amt); const B = Math.min(255, (num & 0x0000FF) + amt); return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); } darkenColor(color, percent) { const num = parseInt(color.replace('#', ''), 16); const amt = Math.round(2.55 * percent); const R = Math.max(0, (num >> 16) - amt); const G = Math.max(0, ((num >> 8) & 0x00FF) - amt); const B = Math.max(0, (num & 0x0000FF) - amt); return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); } gameLoop() { if (this.gameState === 'playing') { this.updatePlayer(); this.updateCreatures(); this.updateParticles(); this.checkCollisions(); } this.draw(); requestAnimationFrame(() => this.gameLoop()); } startGame() { document.getElementById('startScreen').classList.add('hidden'); document.getElementById('gameOverScreen').classList.add('hidden'); document.getElementById('channelButtons').classList.remove('hidden'); document.getElementById('joystick-container').classList.remove('hidden'); this.gameState = 'playing'; tapadnManager.showBannerAd(); } restartGame() { this.score = 0; this.level = 1; this.channel = 1; this.channelScore = 0; this.coins = 0; this.fishes = []; this.particles = []; this.createPlayer(); this.spawnInitialCreatures(); this.updateUI(); document.getElementById('gameOverScreen').classList.add('hidden'); document.getElementById('joystick-container').classList.remove('hidden'); this.gameState = 'playing'; } gameOver() { this.gameState = 'gameover'; document.getElementById('finalScore').innerHTML = `最终得分: ${this.score}`; document.getElementById('joystick-container').classList.add('hidden'); document.getElementById('gameOverScreen').classList.remove('hidden'); document.getElementById('channelButtons').classList.add('hidden'); } } function adjustColor(color, amount) { const hex = color.replace('#', ''); const r = Math.max(0, Math.min(255, parseInt(hex.substr(0, 2), 16) + amount)); const g = Math.max(0, Math.min(255, parseInt(hex.substr(2, 2), 16) + amount)); const b = Math.max(0, Math.min(255, parseInt(hex.substr(4, 2), 16) + amount)); return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; } let game; window.onload = () => { game = new Game(); window.game = game; }; function startGame() { game.startGame(); } function restartGame() { game.restartGame(); }