深海猎手 - 大鱼吃小鱼
by StormViper16815 lines28.5 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>深海猎手 - 大鱼吃小鱼</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: linear-gradient(180deg, #001f3f 0%, #000814 100%);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
touch-action: none;
}
#gameCanvas {
display: block;
cursor: none;
}
.ui-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.menu {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 20, 40, 0.95);
border: 2px solid #00d4ff;
border-radius: 20px;
padding: 40px;
text-align: center;
pointer-events: all;
box-shadow: 0 0 40px rgba(0, 212, 255, 0.3);
backdrop-filter: blur(10px);
}
h1 {
color: #00d4ff;
font-size: 3em;
margin-bottom: 10px;
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
letter-spacing: 3px;
}
.subtitle {
color: #7fdbff;
margin-bottom: 30px;
font-size: 1.1em;
}
.difficulty-selector {
margin: 20px 0;
}
.difficulty-btn {
display: inline-block;
margin: 0 10px;
padding: 12px 30px;
background: transparent;
border: 2px solid #00d4ff;
color: #00d4ff;
border-radius: 25px;
cursor: pointer;
font-size: 1em;
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.difficulty-btn:hover {
background: rgba(0, 212, 255, 0.2);
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(0, 212, 255, 0.4);
}
.difficulty-btn.active {
background: #00d4ff;
color: #001f3f;
box-shadow: 0 0 20px rgba(0, 212, 255, 0.6);
}
.start-btn {
margin-top: 30px;
padding: 15px 60px;
font-size: 1.3em;
background: linear-gradient(45deg, #00d4ff, #0099cc);
border: none;
color: white;
border-radius: 30px;
cursor: pointer;
transition: all 0.3s;
text-transform: uppercase;
letter-spacing: 2px;
font-weight: bold;
}
.start-btn:hover {
transform: scale(1.05);
box-shadow: 0 10px 30px rgba(0, 212, 255, 0.5);
}
.hud {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 1.2em;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
.score {
font-size: 2em;
color: #00d4ff;
font-weight: bold;
}
.size-indicator {
margin-top: 10px;
color: #7fdbff;
}
.game-over {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.9);
border: 2px solid #ff6b6b;
border-radius: 20px;
padding: 40px;
text-align: center;
pointer-events: all;
}
.game-over h2 {
color: #ff6b6b;
font-size: 2.5em;
margin-bottom: 20px;
}
.final-score {
color: white;
font-size: 1.5em;
margin-bottom: 30px;
}
.restart-btn {
padding: 12px 40px;
background: #ff6b6b;
border: none;
color: white;
border-radius: 25px;
cursor: pointer;
font-size: 1.1em;
transition: all 0.3s;
}
.restart-btn:hover {
background: #ff5252;
transform: scale(1.05);
}
.instructions {
margin-top: 20px;
color: #aaa;
font-size: 0.9em;
line-height: 1.6;
}
.bubble {
position: absolute;
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.3), transparent);
border-radius: 50%;
pointer-events: none;
animation: float 4s infinite ease-in-out;
}
@keyframes float {
0%, 100% { transform: translateY(0) translateX(0); }
25% { transform: translateY(-20px) translateX(10px); }
50% { transform: translateY(-10px) translateX(-10px); }
75% { transform: translateY(-30px) translateX(5px); }
}
.combo {
position: absolute;
color: #ffd700;
font-size: 1.5em;
font-weight: bold;
pointer-events: none;
animation: comboAnim 1s forwards;
text-shadow: 0 0 10px rgba(255, 215, 0, 0.8);
}
@keyframes comboAnim {
0% { opacity: 1; transform: scale(1) translateY(0); }
100% { opacity: 0; transform: scale(1.5) translateY(-50px); }
}
</style>
<base target="_blank">
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div class="ui-layer">
<div class="hud" id="hud" style="display: none;">
<div class="score">得分: <span id="scoreValue">0</span></div>
<div class="size-indicator">体型: <span id="sizeValue">小</span></div>
<div style="margin-top: 10px; color: #7fdbff;">连击: x<span id="comboValue">1</span></div>
</div>
<div class="menu" id="mainMenu">
<h1>🐟 深海猎手</h1>
<div class="subtitle">大鱼吃小鱼,适者生存</div>
<div class="difficulty-selector">
<button class="difficulty-btn active" data-diff="easy">简单</button>
<button class="difficulty-btn" data-diff="normal">普通</button>
<button class="difficulty-btn" data-diff="hard">困难</button>
</div>
<button class="start-btn" onclick="startGame()">开始游戏</button>
<div class="instructions">
🖱️ 鼠标控制移动<br>
🐠 吃掉比你小的鱼成长<br>
⚠️ 避开比你大的鱼<br>
🔥 连续吃鱼获得连击加分!
</div>
</div>
<div class="game-over" id="gameOver">
<h2>💀 游戏结束</h2>
<div class="final-score">
最终得分: <span id="finalScore">0</span><br>
最大体型: <span id="finalSize">小</span>
</div>
<button class="restart-btn" onclick="resetGame()">再次挑战</button>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let gameState = 'menu';
let difficulty = 'easy';
let score = 0;
let combo = 0;
let comboTimer = 0;
let lastEatTime = 0;
const difficultySettings = {
easy: { enemySpeed: 1.5, spawnRate: 1500, enemyGrowth: 0.8 },
normal: { enemySpeed: 2.5, spawnRate: 1000, enemyGrowth: 1.0 },
hard: { enemySpeed: 4, spawnRate: 600, enemyGrowth: 1.3 }
};
// 设置画布大小
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// 玩家鱼
const player = {
x: canvas.width / 2,
y: canvas.height / 2,
radius: 15,
color: '#00d4ff',
speed: 0.15,
targetX: canvas.width / 2,
targetY: canvas.height / 2,
angle: 0,
tailAngle: 0
};
// 敌鱼数组
let enemies = [];
// 粒子效果
let particles = [];
// 气泡背景
let bubbles = [];
// 敌鱼类型
const fishTypes = [
{ color: '#ff6b6b', minSize: 8, maxSize: 25, speed: 1, score: 10 },
{ color: '#feca57', minSize: 12, maxSize: 35, speed: 0.8, score: 20 },
{ color: '#48dbfb', minSize: 20, maxSize: 50, speed: 0.6, score: 30 },
{ color: '#ff9ff3', minSize: 30, maxSize: 80, speed: 0.4, score: 50 },
{ color: '#54a0ff', minSize: 50, maxSize: 120, speed: 0.3, score: 100 }
];
class Enemy {
constructor() {
const settings = difficultySettings[difficulty];
const type = fishTypes[Math.floor(Math.random() * fishTypes.length)];
this.radius = type.minSize + Math.random() * (type.maxSize - type.minSize);
// 根据玩家体型调整敌鱼大小分布
if (Math.random() < 0.6) {
// 60%概率生成比玩家小的鱼
this.radius = Math.max(8, player.radius * (0.3 + Math.random() * 0.6));
} else {
// 40%概率生成大鱼
this.radius = player.radius * (1.2 + Math.random() * settings.enemyGrowth);
}
// 从屏幕边缘生成
const side = Math.floor(Math.random() * 4);
switch(side) {
case 0: this.x = -this.radius; this.y = Math.random() * canvas.height; break;
case 1: this.x = canvas.width + this.radius; this.y = Math.random() * canvas.height; break;
case 2: this.x = Math.random() * canvas.width; this.y = -this.radius; break;
case 3: this.x = Math.random() * canvas.width; this.y = canvas.height + this.radius; break;
}
// 朝向屏幕中心移动
const angle = Math.atan2(canvas.height/2 - this.y, canvas.width/2 - this.x) + (Math.random() - 0.5);
const speed = (type.speed * settings.enemySpeed) + Math.random();
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.color = type.color;
this.score = Math.floor(this.radius);
this.angle = angle;
this.tailAngle = 0;
this.tailSpeed = 0.2 + Math.random() * 0.2;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.angle = Math.atan2(this.vy, this.vx);
this.tailAngle += this.tailSpeed;
// 稍微调整方向,增加自然感
this.vx += (Math.random() - 0.5) * 0.1;
this.vy += (Math.random() - 0.5) * 0.1;
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
// 绘制鱼身
const gradient = ctx.createRadialGradient(-this.radius/3, -this.radius/3, 0, 0, 0, this.radius);
gradient.addColorStop(0, lightenColor(this.color, 30));
gradient.addColorStop(1, this.color);
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.ellipse(0, 0, this.radius, this.radius * 0.6, 0, 0, Math.PI * 2);
ctx.fill();
// 绘制尾巴
ctx.fillStyle = this.color;
ctx.beginPath();
const tailWag = Math.sin(this.tailAngle) * 5;
ctx.moveTo(-this.radius * 0.8, 0);
ctx.lineTo(-this.radius * 1.4, -this.radius * 0.4 + tailWag);
ctx.lineTo(-this.radius * 1.4, this.radius * 0.4 + tailWag);
ctx.closePath();
ctx.fill();
// 绘制眼睛
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(this.radius * 0.4, -this.radius * 0.15, this.radius * 0.2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(this.radius * 0.5, -this.radius * 0.15, this.radius * 0.1, 0, Math.PI * 2);
ctx.fill();
// 绘制鱼鳍
ctx.fillStyle = darkenColor(this.color, 20);
ctx.beginPath();
ctx.ellipse(0, this.radius * 0.3, this.radius * 0.3, this.radius * 0.15, 0.5, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
isOffScreen() {
return this.x < -this.radius - 50 || this.x > canvas.width + this.radius + 50 ||
this.y < -this.radius - 50 || this.y > canvas.height + this.radius + 50;
}
}
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.vx = (Math.random() - 0.5) * 4;
this.vy = (Math.random() - 0.5) * 4;
this.life = 1;
this.color = color;
this.size = Math.random() * 4 + 2;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life -= 0.02;
this.vx *= 0.98;
this.vy *= 0.98;
}
draw() {
ctx.save();
ctx.globalAlpha = this.life;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
// 辅助函数:颜色处理
function lightenColor(color, percent) {
const num = parseInt(color.replace("#",""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const G = (num >> 8 & 0x00FF) + amt;
const B = (num & 0x0000FF) + amt;
return "#" + (0x1000000 + (R<255?R<1?0:R:255)*0x10000 + (G<255?G<1?0:G:255)*0x100 + (B<255?B<1?0:B:255)).toString(16).slice(1);
}
function darkenColor(color, percent) {
const num = parseInt(color.replace("#",""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) - amt;
const G = (num >> 8 & 0x00FF) - amt;
const B = (num & 0x0000FF) - amt;
return "#" + (0x1000000 + (R>0?R:0)*0x10000 + (G>0?G:0)*0x100 + (B>0?B:0)).toString(16).slice(1);
}
// 鼠标控制
canvas.addEventListener('mousemove', (e) => {
if (gameState === 'playing') {
player.targetX = e.clientX;
player.targetY = e.clientY;
}
});
canvas.addEventListener('touchmove', (e) => {
if (gameState === 'playing') {
e.preventDefault();
player.targetX = e.touches[0].clientX;
player.targetY = e.touches[0].clientY;
}
});
// 难度选择
document.querySelectorAll('.difficulty-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.difficulty-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
difficulty = this.dataset.diff;
});
});
function startGame() {
document.getElementById('mainMenu').style.display = 'none';
document.getElementById('hud').style.display = 'block';
gameState = 'playing';
// 重置玩家
player.x = canvas.width / 2;
player.y = canvas.height / 2;
player.radius = 15;
player.targetX = canvas.width / 2;
player.targetY = canvas.height / 2;
score = 0;
combo = 0;
enemies = [];
particles = [];
updateHUD();
gameLoop();
// 开始生成敌鱼
spawnEnemy();
}
function spawnEnemy() {
if (gameState !== 'playing') return;
enemies.push(new Enemy());
const settings = difficultySettings[difficulty];
setTimeout(spawnEnemy, settings.spawnRate);
}
function updatePlayer() {
// 平滑移动
const dx = player.targetX - player.x;
const dy = player.targetY - player.y;
player.x += dx * player.speed;
player.y += dy * player.speed;
// 计算角度
if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {
player.angle = Math.atan2(dy, dx);
}
// 尾巴摆动
const speed = Math.sqrt(dx*dx + dy*dy);
player.tailAngle += 0.2 + speed * 0.01;
// 边界限制
player.x = Math.max(player.radius, Math.min(canvas.width - player.radius, player.x));
player.y = Math.max(player.radius, Math.min(canvas.height - player.radius, player.y));
// 连击计时器
if (combo > 0) {
comboTimer--;
if (comboTimer <= 0) {
combo = 0;
updateHUD();
}
}
}
function drawPlayer() {
ctx.save();
ctx.translate(player.x, player.y);
ctx.rotate(player.angle);
// 发光效果
ctx.shadowBlur = 20;
ctx.shadowColor = player.color;
// 身体渐变
const gradient = ctx.createRadialGradient(-player.radius/3, -player.radius/3, 0, 0, 0, player.radius);
gradient.addColorStop(0, '#7fdbff');
gradient.addColorStop(0.5, '#00d4ff');
gradient.addColorStop(1, '#0066cc');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.ellipse(0, 0, player.radius, player.radius * 0.65, 0, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
// 尾巴摆动
ctx.fillStyle = '#00a8cc';
ctx.beginPath();
const tailWag = Math.sin(player.tailAngle) * (player.radius * 0.3);
ctx.moveTo(-player.radius * 0.7, 0);
ctx.lineTo(-player.radius * 1.3, -player.radius * 0.5 + tailWag);
ctx.lineTo(-player.radius * 1.3, player.radius * 0.5 + tailWag);
ctx.closePath();
ctx.fill();
// 背鳍
ctx.fillStyle = '#0099cc';
ctx.beginPath();
ctx.moveTo(-player.radius * 0.2, -player.radius * 0.5);
ctx.lineTo(player.radius * 0.3, -player.radius * 0.9);
ctx.lineTo(player.radius * 0.4, -player.radius * 0.5);
ctx.closePath();
ctx.fill();
// 眼睛(大而有神)
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(player.radius * 0.4, -player.radius * 0.2, player.radius * 0.25, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#001f3f';
ctx.beginPath();
ctx.arc(player.radius * 0.5, -player.radius * 0.2, player.radius * 0.12, 0, Math.PI * 2);
ctx.fill();
// 眼睛高光
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(player.radius * 0.55, -player.radius * 0.25, player.radius * 0.05, 0, Math.PI * 2);
ctx.fill();
// 胸鳍
ctx.fillStyle = '#0088aa';
ctx.beginPath();
ctx.ellipse(0, player.radius * 0.4, player.radius * 0.35, player.radius * 0.2, 0.6, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function checkCollisions() {
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
const dx = player.x - enemy.x;
const dy = player.y - enemy.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < player.radius + enemy.radius * 0.8) {
if (player.radius > enemy.radius) {
// 吃掉敌鱼
eatEnemy(enemy, i);
} else {
// 被吃掉,游戏结束
gameOver();
}
}
}
}
function eatEnemy(enemy, index) {
enemies.splice(index, 1);
// 计算得分(连击加成)
const now = Date.now();
if (now - lastEatTime < 2000) {
combo++;
} else {
combo = 1;
}
lastEatTime = now;
comboTimer = 120; // 2秒内保持连击
const points = Math.floor(enemy.score * (1 + combo * 0.2));
score += points;
// 成长
const growth = enemy.radius * 0.05;
player.radius = Math.min(player.radius + growth, 150);
// 生成粒子效果
for (let i = 0; i < 8; i++) {
particles.push(new Particle(enemy.x, enemy.y, enemy.color));
}
// 显示连击文字
if (combo > 1) {
showComboText(player.x, player.y - player.radius - 20, combo);
}
updateHUD();
}
function showComboText(x, y, comboCount) {
const div = document.createElement('div');
div.className = 'combo';
div.textContent = `${comboCount} 连击!`;
div.style.left = x + 'px';
div.style.top = y + 'px';
document.body.appendChild(div);
setTimeout(() => div.remove(), 1000);
}
function updateHUD() {
document.getElementById('scoreValue').textContent = score;
document.getElementById('comboValue').textContent = combo;
let sizeText = '小';
if (player.radius > 30) sizeText = '中';
if (player.radius > 60) sizeText = '大';
if (player.radius > 100) sizeText = '巨';
if (player.radius > 130) sizeText = '传说';
document.getElementById('sizeValue').textContent = sizeText;
}
function drawBackground() {
// 深海渐变背景
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, '#001f3f');
gradient.addColorStop(0.5, '#001020');
gradient.addColorStop(1, '#000510');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制光线效果
ctx.save();
ctx.globalAlpha = 0.1;
const time = Date.now() * 0.001;
for (let i = 0; i < 5; i++) {
const x = canvas.width * (0.2 + i * 0.15) + Math.sin(time + i) * 50;
const gradient = ctx.createLinearGradient(x, 0, x - 100, canvas.height);
gradient.addColorStop(0, 'rgba(255,255,255,0.3)');
gradient.addColorStop(1, 'transparent');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.moveTo(x - 50, 0);
ctx.lineTo(x + 50, 0);
ctx.lineTo(x - 150, canvas.height);
ctx.lineTo(x - 250, canvas.height);
ctx.closePath();
ctx.fill();
}
ctx.restore();
// 绘制气泡
if (Math.random() < 0.02) {
bubbles.push({
x: Math.random() * canvas.width,
y: canvas.height + 20,
radius: Math.random() * 5 + 2,
speed: Math.random() * 1 + 0.5,
wobble: Math.random() * Math.PI * 2
});
}
ctx.fillStyle = 'rgba(255,255,255,0.1)';
for (let i = bubbles.length - 1; i >= 0; i--) {
const b = bubbles[i];
b.y -= b.speed;
b.wobble += 0.05;
const x = b.x + Math.sin(b.wobble) * 10;
ctx.beginPath();
ctx.arc(x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
if (b.y < -20) bubbles.splice(i, 1);
}
}
function gameLoop() {
if (gameState !== 'playing') return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBackground();
// 更新和绘制敌鱼
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
enemy.update();
enemy.draw();
if (enemy.isOffScreen()) {
enemies.splice(i, 1);
}
}
// 更新和绘制粒子
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.update();
p.draw();
if (p.life <= 0) particles.splice(i, 1);
}
updatePlayer();
drawPlayer();
checkCollisions();
requestAnimationFrame(gameLoop);
}
function gameOver() {
gameState = 'gameover';
document.getElementById('gameOver').style.display = 'block';
document.getElementById('finalScore').textContent = score;
let sizeText = '小';
if (player.radius > 30) sizeText = '中';
if (player.radius > 60) sizeText = '大';
if (player.radius > 100) sizeText = '巨';
if (player.radius > 130) sizeText = '传说';
document.getElementById('finalSize').textContent = sizeText;
}
function resetGame() {
document.getElementById('gameOver').style.display = 'none';
document.getElementById('mainMenu').style.display = 'block';
document.getElementById('hud').style.display = 'none';
gameState = 'menu';
}
// 初始化背景气泡
setInterval(() => {
if (gameState === 'menu' && Math.random() < 0.1) {
const div = document.createElement('div');
div.className = 'bubble';
div.style.width = Math.random() * 20 + 10 + 'px';
div.style.height = div.style.width;
div.style.left = Math.random() * 100 + '%';
div.style.bottom = '-50px';
div.style.animationDuration = (Math.random() * 3 + 4) + 's';
document.body.appendChild(div);
setTimeout(() => div.remove(), 7000);
}
}, 500);
</script>
</body>
</html>Game Source: 深海猎手 - 大鱼吃小鱼
Creator: StormViper16
Libraries: none
Complexity: complex (815 lines, 28.5 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-stormviper16" to link back to the original. Then publish at arcadelab.ai/publish.