呼行视点 知研 打海盗小游戏,打海盗小游戏

打海盗小游戏,打海盗小游戏

打海盗小游戏,打海盗小游戏

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">⚓ 打海盗 · 小游戏</title>
    <style>
        * {
            box-sizing: border-box;
            user-select: none;
        }
        body {
            margin: 0;
            min-height: 100vh;
            background: linear-gradient(145deg, #0a1f2e 0%, #1b3a4b 100%);
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
        }
        .game-wrapper {
            background: #1e2f3a;
            border-radius: 2rem;
            padding: 1.2rem 1.2rem 1.5rem;
            box-shadow: 0 20px 30px rgba(0, 0, 0, 0.6), inset 0 0 0 2px #5d8b9c;
        }
        canvas {
            display: block;
            width: 800px;
            height: 500px;
            border-radius: 1.2rem;
            box-shadow: inset 0 0 30px #00000055, 0 10px 20px #00000080;
            cursor: crosshair;
            background: #0e2632;
            background-image: radial-gradient(circle at 20% 20%, #2e5e6e33 0%, transparent 30%),
                              radial-gradient(circle at 90% 70%, #4e8a9e33 0%, transparent 40%);
        }
        .ui-panel {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 1rem;
            color: #e9f2f0;
            text-shadow: 0 2px 3px black;
            font-weight: 600;
            padding: 0 0.5rem;
        }
        .stats {
            display: flex;
            gap: 2.2rem;
            background: #0d1f26;
            padding: 0.6rem 1.8rem;
            border-radius: 3rem;
            border: 1px solid #3e6b7c;
            box-shadow: inset 0 0 8px #00000070;
        }
        .stat-item {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            font-size: 1.4rem;
        }
        .stat-icon {
            font-size: 1.6rem;
            filter: drop-shadow(0 3px 3px black);
        }
        button {
            background: #dc6e3b;
            border: none;
            color: #fff4e6;
            font-weight: bold;
            font-size: 1.3rem;
            padding: 0.6rem 2.2rem;
            border-radius: 3rem;
            cursor: pointer;
            box-shadow: 0 7px 0 #7a3418, 0 10px 18px black;
            transition: 0.05s linear;
            letter-spacing: 1px;
            display: flex;
            align-items: center;
            gap: 0.6rem;
            border: 1px solid #ffb085;
            text-shadow: 0 2px 0 #632f0e;
        }
        button:active {
            transform: translateY(5px);
            box-shadow: 0 2px 0 #7a3418, 0 8px 18px black;
        }
        button:disabled {
            opacity: 0.6;
            transform: translateY(4px);
            box-shadow: 0 3px 0 #7a3418, 0 8px 18px black;
            pointer-events: none;
        }
        .hp-bar {
            display: flex;
            align-items: center;
            gap: 0.8rem;
            background: #0d1f26;
            padding: 0.5rem 1.2rem;
            border-radius: 3rem;
            border: 1px solid #b34e4e;
            box-shadow: inset 0 0 8px #00000070;
        }
        .hp-icon {
            font-size: 1.8rem;
        }
        .hp-text {
            font-size: 1.4rem;
            min-width: 3.2rem;
            text-align: center;
            color: #fbc2c2;
            font-weight: 800;
        }
        .hp-bar-fill {
            width: 120px;
            height: 18px;
            background: #2e1a1a;
            border-radius: 20px;
            overflow: hidden;
            border: 1px solid #a55555;
            box-shadow: inset 0 2px 5px black;
        }
        .hp-bar-inner {
            height: 100%;
            width: 100%;
            background: linear-gradient(90deg, #e03a3a, #b81f1f);
            border-radius: 20px;
            transition: width 0.15s ease;
            box-shadow: 0 0 12px #ff6565;
        }
    </style>
</head>
<body>
<div class="game-wrapper">
    <canvas id="gameCanvas" width="800" height="500"></canvas>
    <div class="ui-panel">
        <div class="stats">
            <div class="stat-item">
                <span class="stat-icon">☠️</span>
                <span id="scoreDisplay">0</span>
            </div>
            <div class="stat-item">
                <span class="stat-icon">🔫</span>
                <span id="shotsDisplay">0</span>
            </div>
        </div>
        <div class="hp-bar">
            <span class="hp-icon">❤️</span>
            <div class="hp-bar-fill">
                <div id="hpBarInner" class="hp-bar-inner" style="width: 100%"></div>
            </div>
            <span id="hpText" class="hp-text">100</span>
        </div>
        <button id="restartBtn">
            <span>⚓</span> 重新出海
        </button>
    </div>
</div>
<script>
    (function() {
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const scoreDisplay = document.getElementById('scoreDisplay');
        const shotsDisplay = document.getElementById('shotsDisplay');
        const hpBarInner = document.getElementById('hpBarInner');
        const hpText = document.getElementById('hpText');
        const restartBtn = document.getElementById('restartBtn');
        // ----- 游戏配置 -----
        const CANVAS_W = 800, CANVAS_H = 500;
        const PLAYER_RADIUS = 22;          // 炮台/船的大小
        const PIRATE_RADIUS = 26;          // 海盗半径
        const BULLET_RADIUS = 5;
        const BULLET_SPEED = 7.8;
        const PIRATE_SPEED = 1.2;
        const MAX_PIRATES = 8;             // 同时最多海盗数量
        const PLAYER_MAX_HP = 100;
        const SPAWN_INTERVAL_FRAMES = 50;  // 每50帧生成一个海盗 (约0.8~1秒60fps)
        const DAMAGE_PER_PIRATE = 10;      // 每个海盗撞到玩家造成的伤害
        // ----- 游戏状态 -----
        let player = {
            x: CANVAS_W / 2,
            y: CANVAS_H / 2,
            hp: PLAYER_MAX_HP,
            maxHp: PLAYER_MAX_HP,
            radius: PLAYER_RADIUS,
            invincibleFrames: 0        // 受伤后的短暂无敌闪烁,防止瞬间多次扣血
        };
        let pirates = [];              // 海盗对象数组
        let bullets = [];             // 子弹对象数组
        let score = 0;
        let shotsFired = 0;           // 总开火次数 (统计用)
        let frameCounter = 0;         // 用于生成计时
        let gameOver = false;
        let gameWin = false;          // 无胜利条件,仅用于表示游戏结束
        // 鼠标/触摸位置 (用于炮口指向)
        let aimX = CANVAS_W / 2;
        let aimY = CANVAS_H / 2;
        let mouseInside = false;      // 是否在canvas内,用于绘制准星
        // 无敌闪烁计时
        const INVINCIBLE_DURATION = 25; // 帧数
        // ----- 辅助函数 -----
        function clamp(value, min, max) {
            return Math.min(max, Math.max(min, value));
        }
        // 重置游戏 (保留鼠标位置不变)
        function resetGame() {
            gameOver = false;
            gameWin = false;
            player = {
                x: CANVAS_W / 2,
                y: CANVAS_H / 2,
                hp: PLAYER_MAX_HP,
                maxHp: PLAYER_MAX_HP,
                radius: PLAYER_RADIUS,
                invincibleFrames: 0
            };
            pirates = [];
            bullets = [];
            score = 0;
            shotsFired = 0;
            frameCounter = 0;
            // 初始生成2个海盗,让游戏不那麼空
            for (let i = 0; i < 2; i++) {
                spawnPirate();
            }
            updateUI();
        }
        // 生成一个海盗 (位置避开玩家太近)
        function spawnPirate() {
            if (pirates.length >= MAX_PIRATES) return;
            let spawnX, spawnY;
            let maxAttempts = 100;
            let attempts = 0;
            const minDistFromPlayer = 180;  // 离玩家至少180像素
            do {
                // 主要在画布边缘附近生成,看起来像从外面驶入
                const edge = Math.floor(Math.random() * 4);
                if (edge === 0) { // 上边缘
                    spawnX = Math.random() * CANVAS_W;
                    spawnY = -PIRATE_RADIUS * 2;
                } else if (edge === 1) { // 下边缘
                    spawnX = Math.random() * CANVAS_W;
                    spawnY = CANVAS_H + PIRATE_RADIUS * 2;
                } else if (edge === 2) { // 左边缘
                    spawnX = -PIRATE_RADIUS * 2;
                    spawnY = Math.random() * CANVAS_H;
                } else { // 右边缘
                    spawnX = CANVAS_W + PIRATE_RADIUS * 2;
                    spawnY = Math.random() * CANVAS_H;
                }
                attempts++;
                // 如果离玩家太近,再试一次 (但减少尝试次数避免死循环)
            } while (Math.hypot(spawnX - player.x, spawnY - player.y) < minDistFromPlayer && attempts < maxAttempts);
            // 如果尝试太多次还没找到位置,就直接在边缘随机位置
            if (attempts >= maxAttempts) {
                if (Math.random() < 0.5) {
                    spawnX = Math.random() < 0.5 ? -PIRATE_RADIUS * 2 : CANVAS_W + PIRATE_RADIUS * 2;
                    spawnY = Math.random() * CANVAS_H;
                } else {
                    spawnX = Math.random() * CANVAS_W;
                    spawnY = Math.random() < 0.5 ? -PIRATE_RADIUS * 2 : CANVAS_H + PIRATE_RADIUS * 2;
                }
            }
            // 随机速度方向朝向玩家 (稍后每帧更新朝向玩家,但生成时先给个方向)
            const angleToPlayer = Math.atan2(player.y - spawnY, player.x - spawnX);
            // 海盗不直接朝向玩家,我们使用追踪逻辑:速度方向会在update里计算。
            pirates.push({
                x: spawnX,
                y: spawnY,
                radius: PIRATE_RADIUS,
                angle: 0,        // 用于绘制方向
                speed: PIRATE_SPEED,
                // 添加一点随机偏移,让不同海盗速度稍微不同
                speedFactor: 0.85 + Math.random() * 0.5,
            });
        }
        // 开火: 从玩家位置向瞄准点发射一颗子弹
        function fireBullet(targetX, targetY) {
            if (gameOver) return;
            // 计算方向
            let dx = targetX - player.x;
            let dy = targetY - player.y;
            const length = Math.hypot(dx, dy);
            if (length < 0.001) return;   // 零向量
            const normDx = dx / length;
            const normDy = dy / length;
            // 子弹从玩家边缘出发 (避免卡在身体里)
            const startX = player.x + normDx * (player.radius + 2);
            const startY = player.y + normDy * (player.radius + 2);
            bullets.push({
                x: startX,
                y: startY,
                vx: normDx * BULLET_SPEED,
                vy: normDy * BULLET_SPEED,
                radius: BULLET_RADIUS,
                life: 1,           // 存活标记
            });
            shotsFired++;
            updateUI();
        }
        // 更新UI (分数、血量、射击数)
        function updateUI() {
            scoreDisplay.textContent = score;
            shotsDisplay.textContent = shotsFired;
            const hpPercent = Math.max(0, (player.hp / player.maxHp) * 100);
            hpBarInner.style.width = `${hpPercent}%`;
            hpText.textContent = Math.max(0, player.hp);
            // 根据血量改变血条颜色
            if (hpPercent > 60) {
                hpBarInner.style.background = 'linear-gradient(90deg, #e03a3a, #b81f1f)';
            } else if (hpPercent > 30) {
                hpBarInner.style.background = 'linear-gradient(90deg, #e08a3a, #b85f1f)';
            } else {
                hpBarInner.style.background = 'linear-gradient(90deg, #a51f1f, #6e1010)';
            }
        }
        // ----- 玩家受伤处理 (带无敌) -----
        function damagePlayer(amount) {
            if (gameOver) return;
            if (player.invincibleFrames > 0) return;  // 无敌状态
            player.hp = Math.max(0, player.hp - amount);
            player.invincibleFrames = INVINCIBLE_DURATION;
            updateUI();
            if (player.hp <= 0) {
                gameOver = true;
                player.hp = 0;
                updateUI();
            }
        }
        // ----- 更新逻辑 (每帧) -----
        function update() {
            if (gameOver) return;
            // 1. 减少无敌帧
            if (player.invincibleFrames > 0) {
                player.invincibleFrames--;
            }
            // 2. 生成海盗 (使用frameCounter)
            frameCounter++;
            if (frameCounter % SPAWN_INTERVAL_FRAMES === 0) {
                spawnPirate();
            }
            // 3. 海盗移动 (向玩家靠近)
            for (let i = pirates.length - 1; i >= 0; i--) {
                const p = pirates[i];
                // 计算指向玩家的方向向量
                let dx = player.x - p.x;
                let dy = player.y - p.y;
                const dist = Math.hypot(dx, dy);
                // 如果海盗已经非常接近玩家 (碰撞处理),先标记移除并造成伤害
                if (dist < player.radius + p.radius) {
                    // 碰撞!造成伤害
                    damagePlayer(DAMAGE_PER_PIRATE);
                    // 移除该海盗 (爆炸效果可以不加,但为了简单直接移除)
                    pirates.splice(i, 1);
                    continue; // 跳过后续移动
                }
                // 否则向玩家移动
                if (dist > 0.5) {
                    const moveX = (dx / dist) * p.speed * p.speedFactor;
                    const moveY = (dy / dist) * p.speed * p.speedFactor;
                    p.x += moveX;
                    p.y += moveY;
                    // 更新海盗的角度(用于绘制朝向)
                    p.angle = Math.atan2(dy, dx);
                }
                // 边界限制?海盗从外面进来,后面如果跑到外面去也保留吧,但为了美观稍微限制在画布内?不限制反而自然。
                // 不限制它们,如果它们跑出去太远,但玩家在中心,一般不会。
            }
            // 4. 子弹移动 & 碰撞检测
            for (let i = bullets.length - 1; i >= 0; i--) {
                const b = bullets[i];
                b.x += b.vx;
                b.y += b.vy;
                // 如果子弹超出画布太远则移除
                if (b.x < -50 || b.x > CANVAS_W + 50 || b.y < -50 || b.y > CANVAS_H + 50) {
                    bullets.splice(i, 1);
                    continue;
                }
                let bulletUsed = false;
                // 检查是否击中海盗
                for (let j = pirates.length - 1; j >= 0; j--) {
                    const p = pirates[j];
                    const distToPirate = Math.hypot(b.x - p.x, b.y - p.y);
                    if (distToPirate < b.radius + p.radius) {
                        // 击中!
                        pirates.splice(j, 1);
                        bulletUsed = true;
                        score += 10;          // 每击中一个海盗得10分
                        updateUI();
                        break;                // 一颗子弹只打中一个海盗
                    }
                }
                if (bulletUsed) {
                    bullets.splice(i, 1);
                }
            }
            // 5. 如果玩家生命值归零则游戏结束 (已处理)
            if (player.hp <= 0) {
                gameOver = true;
            }
            // 如果海盗数量为0且游戏未结束,为了不太无聊,可以补充生成,但生成计时器会做;初始和死亡时已有生成
            // 不过如果场上没有海盗了,稍微加快生成?但保持自然,计时器继续工作。
        }
        // ----- 绘制所有内容 -----
        function draw() {
            ctx.clearRect(0, 0, CANVAS_W, CANVAS_H);
            // 绘制海洋网格/波纹 (氛围)
            ctx.strokeStyle = '#5f9eae30';
            ctx.lineWidth = 1.5;
            for (let i = 0; i < CANVAS_W; i += 45) {
                ctx.beginPath();
                ctx.moveTo(i, 0);
                ctx.lineTo(i + 15, CANVAS_H);
                ctx.strokeStyle = '#3f7f8f20';
                ctx.stroke();
            }
            // 水位光晕
            ctx.fillStyle = '#163a4660';
            ctx.beginPath();
            ctx.arc(150, 80, 120, 0, Math.PI * 2);
            ctx.fill();
            ctx.beginPath();
            ctx.arc(650, 400, 150, 0, Math.PI * 2);
            ctx.fillStyle = '#1a405060';
            ctx.fill();
            // 1. 绘制子弹 (发光的炮弹)
            for (const b of bullets) {
                ctx.shadowColor = '#f0e68c';
                ctx.shadowBlur = 18;
                ctx.beginPath();
                ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
                ctx.fillStyle = '#ffd966';
                ctx.fill();
                // 内部亮色
                ctx.shadowBlur = 10;
                ctx.beginPath();
                ctx.arc(b.x - 1, b.y - 1, b.radius * 0.6, 0, Math.PI * 2);
                ctx.fillStyle = '#fff8e7';
                ctx.fill();
            }
            ctx.shadowBlur = 0; // 重置阴影
            // 2. 绘制海盗 (骷髅海盗船/海盗旗形象)
            for (const p of pirates) {
                // 海盗船体 (圆形为主体)
                ctx.shadowColor = '#000000';
                ctx.shadowBlur = 12;
                // 船身暗红色
                ctx.beginPath();
                ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
                ctx.fillStyle = '#4a2e1e';
                ctx.fill();
                // 甲板
                ctx.beginPath();
                ctx.arc(p.x, p.y, p.radius * 0.8, 0, Math.PI * 2);
                ctx.fillStyle = '#6b4a32';
                ctx.fill();
                // 海盗旗 (骷髅标志)
                ctx.shadowBlur = 8;
                // 旗杆
                ctx.beginPath();
                ctx.moveTo(p.x + 10, p.y - 20);
                ctx.lineTo(p.x + 28, p.y - 32);
                ctx.lineWidth = 4;
                ctx.strokeStyle = '#c0a080';
                ctx.stroke();
                // 旗子 (黑底)
                ctx.beginPath();
                ctx.moveTo(p.x + 28, p.y - 32);
                ctx.lineTo(p.x + 42, p.y - 28);
                ctx.lineTo(p.x + 28, p.y - 14);
                ctx.closePath();
                ctx.fillStyle = '#1a1a1a';
                ctx.fill();
                // 骷髅图案简笔
                ctx.beginPath();
                ctx.arc(p.x + 32, p.y - 24, 4, 0, Math.PI * 2);
                ctx.fillStyle = '#eee';
                ctx.fill();
                ctx.beginPath();
                ctx.arc(p.x + 30, p.y - 25, 1.5, 0, Math.PI * 2);
                ctx.fillStyle = '#000';
                ctx.fill();
                ctx.beginPath();
                ctx.arc(p.x + 34, p.y - 25, 1.5, 0, Math.PI * 2);
                ctx.fillStyle = '#000';
                ctx.fill();
                // 交叉骨头
                ctx.beginPath();
                ctx.moveTo(p.x + 26, p.y - 28);
                ctx.lineTo(p.x + 32, p.y - 22);
                ctx.moveTo(p.x + 32, p.y - 28);
                ctx.lineTo(p.x + 26, p.y - 22);
                ctx.lineWidth = 2;
                ctx.strokeStyle = '#ddd';
                ctx.stroke();
                // 海盗眼睛 (发红光)
                ctx.shadowBlur = 15;
                ctx.shadowColor = '#ff0000';
                ctx.beginPath();
                ctx.arc(p.x - 6, p.y - 4, 5, 0, Math.PI * 2);
                ctx.fillStyle = '#111';
                ctx.fill();
                ctx.beginPath();
                ctx.arc(p.x + 8, p.y - 4, 5, 0, Math.PI * 2);
                ctx.fillStyle = '#111';
                ctx.fill();
                ctx.beginPath();
                ctx.arc(p.x - 7, p.y - 5, 2, 0, Math.PI * 2);
                ctx.fillStyle = '#ff4444';
                ctx.fill();
                ctx.beginPath();
                ctx.arc(p.x + 7, p.y - 5, 2, 0, Math.PI * 2);
                ctx.fillStyle = '#ff4444';
                ctx.fill();
                // 眼罩带子
                ctx.beginPath();
                ctx.moveTo(p.x - 12, p.y - 9);
                ctx.lineTo(p.x + 14, p.y - 1);
                ctx.lineWidth = 2.5;
                ctx.strokeStyle = '#222';
                ctx.stroke();
                // 嘴
                ctx.beginPath();
                ctx.arc(p.x, p.y + 8, 8, 0.2, Math.PI - 0.2);
                ctx.lineWidth = 2.5;
                ctx.strokeStyle = '#a55';
                ctx.stroke();
                // 重置阴影,避免影响后续
                ctx.shadowBlur = 0;
                ctx.shadowColor = 'transparent';
            }
            // 3. 绘制玩家 (炮台/船)
            // 无敌闪烁效果:如果invincibleFrames > 0 且为奇数帧则半透明
            let drawPlayer = true;
            if (player.invincibleFrames > 0 && Math.floor(player.invincibleFrames / 4) % 2 === 0) {
                // 闪烁时稍微透明,更明显
                ctx.globalAlpha = 0.55;
            } else {
                ctx.globalAlpha = 1.0;
            }
            // 玩家船体
            ctx.shadowColor = '#000000';
            ctx.shadowBlur = 18;
            ctx.beginPath();
            ctx.arc(player.x, player.y, player.radius, 0, Math.PI * 2);
            ctx.fillStyle = '#2a6d7c';
            ctx.fill();
            // 甲板
            ctx.beginPath();
            ctx.arc(player.x, player.y, player.radius * 0.8, 0, Math.PI * 2);
            ctx.fillStyle = '#4a9eb0';
            ctx.fill();
            // 炮口 (指向鼠标)
            let angle = Math.atan2(aimY - player.y, aimX - player.x);
            if (!mouseInside) {
                // 如果鼠标不在画布内,炮口默认朝上或者随机角度?就保持上次角度吧(默认会指向最后鼠标位置)
                // 但如果还没移动过鼠标,aimX/Y是画布中心,指向中心方向无意义,那就指向右方
                if (aimX === CANVAS_W/2 && aimY === CANVAS_H/2) {
                    angle = 0; // 向右
                }
            }
            const barrelLength = player.radius * 1.2;
            const barrelX = player.x + Math.cos(angle) * barrelLength;
            const barrelY = player.y + Math.sin(angle) * barrelLength;
            // 绘制炮管
            ctx.beginPath();
            ctx.moveTo(player.x + Math.cos(angle) * (player.radius * 0.6), player.y + Math.sin(angle) * (player.radius * 0.6));
            ctx.lineTo(barrelX, barrelY);
            ctx.lineWidth = 10;
            ctx.strokeStyle = '#3e3e3e';
            ctx.stroke();
            ctx.lineWidth = 6;
            ctx.strokeStyle = '#7a7a7a';
            ctx.stroke();
            // 炮口亮点
            ctx.beginPath();
            ctx.arc(barrelX, barrelY, 6, 0, Math.PI * 2);
            ctx.fillStyle = '#bba76a';
            ctx.shadowBlur = 20;
            ctx.fill();
            // 玩家眼睛 (可爱一点)
            ctx.shadowBlur = 0;
            ctx.beginPath();
            ctx.arc(player.x - 7, player.y - 8, 4, 0, Math.PI * 2);
            ctx.fillStyle = '#ffffff';
            ctx.fill();
            ctx.beginPath();
            ctx.arc(player.x + 7, player.y - 8, 4, 0, Math.PI * 2);
            ctx.fillStyle = '#ffffff';
            ctx.fill();
            ctx.beginPath();
            ctx.arc(player.x - 7, player.y - 8, 2, 0, Math.PI * 2);
            ctx.fillStyle = '#000000';
            ctx.fill();
            ctx.beginPath();
            ctx.arc(player.x + 7, player.y - 8, 2, 0, Math.PI * 2);
            ctx.fillStyle = '#000000';
            ctx.fill();
            ctx.globalAlpha = 1.0;
            ctx.shadowBlur = 0;
            // 4. 瞄准点 / 准星 (如果鼠标在canvas内)
            if (mouseInside) {
                ctx.beginPath();
                ctx.arc(aimX, aimY, 10, 0, Math.PI * 2);
                ctx.strokeStyle = '#f0f0f0cc';
                ctx.lineWidth = 2;
                ctx.setLineDash([5, 6]);
                ctx.stroke();
                ctx.setLineDash([]); // 重置实线
                ctx.beginPath();
                ctx.moveTo(aimX - 16, aimY);
                ctx.lineTo(aimX - 6, aimY);
                ctx.moveTo(aimX + 6, aimY);
                ctx.lineTo(aimX + 16, aimY);
                ctx.moveTo(aimX, aimY - 16);
                ctx.lineTo(aimX, aimY - 6);
                ctx.moveTo(aimX, aimY + 6);
                ctx.lineTo(aimX, aimY + 16);
                ctx.strokeStyle = '#ffd966';
                ctx.lineWidth = 2.5;
                ctx.stroke();
            }
            // 5. 游戏结束覆盖层
            if (gameOver) {
                ctx.fillStyle = '#000000aa';
                ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
                ctx.font = 'bold 58px "Segoe UI", system-ui, sans-serif';
                ctx.fillStyle = '#e03a3a';
                ctx.shadowColor = 'black';
                ctx.shadowBlur = 20;
                ctx.textAlign = 'center';
                ctx.fillText('☠️ 沉船了 ☠️', CANVAS_W/2, CANVAS_H/2 - 30);
                ctx.font = 'bold 32px "Segoe UI", system-ui, sans-serif';
                ctx.fillStyle = '#ffe484';
                ctx.fillText('点击"重新出海"', CANVAS_W/2, CANVAS_H/2 + 60);
                ctx.shadowBlur = 0;
            }
            // 显示海盗数量 (debug或者提示)
            // 可选,不显示也行。
        }
        // ----- 动画循环 -----
        function gameLoop() {
            if (!gameOver) {
                update();
            }
            draw();
            requestAnimationFrame(gameLoop);
        }
        // ----- 事件绑定 -----
        function handleMouseMove(e) {
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;   // canvas物理像素比
            const scaleY = canvas.height / rect.height;
            const rawX = (e.clientX - rect.left) * scaleX;
            const rawY = (e.clientY - rect.top) * scaleY;
            aimX = clamp(rawX, 0, CANVAS_W);
            aimY = clamp(rawY, 0, CANVAS_H);
            mouseInside = true;
        }
        function handleMouseLeave() {
            mouseInside = false;
            // 也可以把瞄准位置重置到玩家前方?但保留最后位置更自然
        }
        function handleCanvasClick(e) {
            if (gameOver) return;
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;
            const clickX = (e.clientX - rect.left) * scaleX;
            const clickY = (e.clientY - rect.top) * scaleY;
            // 更新瞄准位置再开火 (确保开火方向准确)
            aimX = clamp(clickX, 0, CANVAS_W);
            aimY = clamp(clickY, 0, CANVAS_H);
            fireBullet(aimX, aimY);
        }
        // 触摸支持
        function handleTouchStart(e) {
            e.preventDefault();  // 防止滚动
            const touch = e.touches[0];
            if (!touch) return;
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;
            const touchX = (touch.clientX - rect.left) * scaleX;
            const touchY = (touch.clientY - rect.top) * scaleY;
            aimX = clamp(touchX, 0, CANVAS_W);
            aimY = clamp(touchY, 0, CANVAS_H);
            mouseInside = true;
            if (!gameOver) {
                fireBullet(aimX, aimY);
            }
        }
        function handleTouchMove(e) {
            e.preventDefault();
            const touch = e.touches[0];
            if (!touch) return;
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;
            const touchX = (touch.clientX - rect.left) * scaleX;
            const touchY = (touch.clientY - rect.top) * scaleY;
            aimX = clamp(touchX, 0, CANVAS_W);
            aimY = clamp(touchY, 0, CANVAS_H);
            mouseInside = true;
        }
        function handleTouchEnd(e) {
            e.preventDefault();
            // 不重置mouseInside,只是为了下次触摸还在
        }
        // ----- 初始化与注册事件 -----
        canvas.addEventListener('mousemove', handleMouseMove);
        canvas.addEventListener('mouseleave', handleMouseLeave);
        canvas.addEventListener('click', handleCanvasClick);
        canvas.addEventListener('touchstart', handleTouchStart, { passive: false });
        canvas.addEventListener('touchmove', handleTouchMove, { passive: false });
        canvas.addEventListener('touchend', handleTouchEnd);
        restartBtn.addEventListener('click', () => {
            resetGame();
        });
        // 启动游戏
        resetGame();
        // 初始鼠标位置为玩家右方,让炮口朝右
        aimX = player.x + 100;
        aimY = player.y;
        gameLoop();
    })();
</script>
</body>
</html>

玩法与操作说明

您可以点击画布上的任意位置来发射炮弹,目标是击中不断靠近的海盗船。

  • 核心玩法:您控制画面中央的炮台,海盗会从屏幕边缘向您靠近,您需要点击鼠标或触摸屏幕来瞄准并发射炮弹,每击中一个海盗可获得10分。
  • 生命与反馈:屏幕上方显示您的生命值,如果海盗碰到您的炮台,生命值会减少,同时炮台会短暂闪烁,生命值归零则游戏结束。
  • 重新开始:游戏结束后,点击右下角的“重新出海”按钮即可开始新一局。

优化建议: 您可以根据喜好调整游戏难度,在代码开头的“游戏配置”区域,修改 PIRATE_SPEED(海盗速度)或 MAX_PIRATES(海盗数量)来改变挑战性。

打海盗小游戏,打海盗小游戏

打海盗小游戏,打海盗小游戏

本文来自网络,不代表大媒体立场,转载请注明出处:https://tq.hushing.net/news-m/3651.html

作者: admin

呼行擅长以细腻笔触描绘现代人情感困境/呼行以独特视角解读科技人文交叉领域
下一篇
http://hushing.net/zb_users/upload/2026/11/20261114222806179466648664409.jpg

你们steam有多少游戏,你的Steam库有多少款游戏?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

工作时间:周一至周五,9:00-17:30,节假日休息

微信扫一扫关注我们