<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D第一人称射击游戏</title>
    <style>
        body { margin: 0; overflow: hidden; }
        #crosshair {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 10px;
            height: 10px;
            background-color: red;
            border-radius: 50%;
        }
        .ui { font-family: Arial, sans-serif; color: white; font-size: 20px; position: absolute; }
    </style>
</head>
<body>
<!-- 准星 -->
<div id="crosshair"></div>
<!-- 受伤闪烁效果 -->
<div id="damageOverlay" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 0, 0, 0); pointer-events: none;"></div>
<!-- 游戏结束弹窗 -->
<div id="gameOverModal" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.8); color: white; padding: 20px; border-radius: 10px; text-align: center; display: none;">
    <h2>游戏结束</h2>
    <p id="finalScore"></p>
    <button id="restartButton" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">重新开始</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>
<script>
    let isGamePaused = false;
    // 初始化场景
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.set(0, 1.6, 0); // 玩家高度1.6米

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // 添加光照
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
    directionalLight.position.set(1, 1, 1);
    scene.add(directionalLight);

    // 视角控制(重新设计)
    let yaw = 0; // 水平旋转角度(左右)
    let pitch = 0; // 垂直旋转角度(上下)
    const sensitivity = 0.002; // 鼠标灵敏度
    document.addEventListener('click', () => {
        renderer.domElement.requestPointerLock();
    });
    document.addEventListener('mousemove', (event) => {
        if (document.pointerLockElement === renderer.domElement) {
            const movementX = event.movementX || 0;
            const movementY = event.movementY || 0;

            // 水平旋转(向左移动鼠标镜头向左)
            yaw -= movementX * sensitivity;

            // 垂直旋转(向上移动鼠标镜头向上)
            pitch -= movementY * sensitivity;
            pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, pitch)); // 限制垂直角度

            // 更新相机旋转
            camera.rotation.order = 'YXZ'; // 先Y(水平)后X(垂直)
            camera.rotation.set(pitch, yaw, 0);
        }
    });


    // WASD移动控制
    const keys = {};
    document.addEventListener('keydown', (event) => keys[event.key.toLowerCase()] = true);
    document.addEventListener('keyup', (event) => keys[event.key.toLowerCase()] = false);

    // 子弹管理
    let bullets = 10;
    const maxBullets = 10;
    const bulletDisplay = document.createElement('div');
    bulletDisplay.className = 'ui';
    bulletDisplay.style.bottom = '10px';
    bulletDisplay.style.right = '10px';
    bulletDisplay.innerText = `子弹: ${bullets}`;
    document.body.appendChild(bulletDisplay);

    // 生命值
    let health = 100;
    const healthDisplay = document.createElement('div');
    healthDisplay.className = 'ui';
    healthDisplay.style.bottom = '10px';
    healthDisplay.style.left = '10px';
    healthDisplay.innerText = `生命值: ${health}`;
    document.body.appendChild(healthDisplay);

    function deductHealth(amount) {
        if (isGamePaused) return; // 如果已暂停,不再扣血
        health -= amount;
        healthDisplay.innerText = `生命值: ${health}`;

        // 触发红色闪烁效果
        const damageOverlay = document.getElementById('damageOverlay');
        gsap.fromTo(
            damageOverlay,
            { backgroundColor: 'rgba(255, 0, 0, 0.5)' },
            {
                backgroundColor: 'rgba(255, 0, 0, 0)',
                duration: 0.5,
                ease: 'power1.out'
            }
        );

        if (health <= 0) {
            endGame();
        }
    }

    // 枪械展示
    const gunTexture = new THREE.TextureLoader().load('./image/gun.png'); // 替换为实际枪械图片URL
    const gunMaterial = new THREE.MeshBasicMaterial({ map: gunTexture, transparent: true });
    const gunGeometry = new THREE.PlaneGeometry(1, 1);
    const gun = new THREE.Mesh(gunGeometry, gunMaterial);
    gun.position.set(0.5, -0.5, -1);
    camera.add(gun);
    scene.add(camera);

    // 开枪函数
    function shoot() {
        if (bullets > 0) {
            bullets--;
            bulletDisplay.innerText = `子弹: ${bullets}`;
            gsap.to(gun.position, { z: -1.2, duration: 0.05, yoyo: true, repeat: 1 });

            const raycaster = new THREE.Raycaster();
            raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
            const intersects = raycaster.intersectObjects(enemies.map(e => e.sprite));

            if (intersects.length > 0) {
                const enemy = intersects[0].object;
                const hitDirection = new THREE.Vector3()
                    .subVectors(enemy.position, camera.position)
                    .normalize();

                // 添加击飞效果
                gsap.to(enemy.position, {
                    x: enemy.position.x + hitDirection.x * 5,  // 沿射击方向飞5个单位
                    y: enemy.position.y + 2,                   // 向上飞2个单位
                    z: enemy.position.z + hitDirection.z * 5,
                    duration: 0.3,                             // 飞行时间0.3秒
                    ease: "power2.out",                       // 减速效果
                    onComplete: () => {
                        scene.remove(enemy);                  // 动画完成后移除敌人
                        enemies = enemies.filter(e => e.sprite !== enemy);
                    }
                });

                // 同时淡出敌人
                gsap.to(enemy.material, {
                    opacity: 0,
                    duration: 0.3,
                    onComplete: () => {
                        enemy.material.opacity = 1;           // 重置透明度以备复用
                    }
                });

                score += 20;
                timerDisplay.innerText = `时间: ${timeLeft} | 分数: ${score}`;
            }
        }
    }
    document.addEventListener('click', shoot);
    document.addEventListener('keydown', (event) => {
        if (event.key === 'r' && bullets < maxBullets) {
            bullets = maxBullets;
            bulletDisplay.innerText = `子弹: ${bullets}`;
        }
    });

    // 地形生成
    const noise = new SimplexNoise();
    const terrainSize = 100;
    const terrainDetail = 0.1;
    const terrainHeight = 1; // 平缓地形
    const geometry = new THREE.PlaneGeometry(terrainSize, terrainSize, 100, 100);
    geometry.rotateX(-Math.PI / 2);
    const vertices = geometry.attributes.position.array;
    for (let i = 0; i < vertices.length; i += 3) {
        const x = vertices[i];
        const z = vertices[i + 2];
        const y = noise.noise2D(x * terrainDetail, z * terrainDetail) * terrainHeight;
        vertices[i + 1] = y;
    }
    geometry.computeVertexNormals();
    const terrainMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
    const terrain = new THREE.Mesh(geometry, terrainMaterial);
    scene.add(terrain);

    // 射线投射器
    const raycaster = new THREE.Raycaster();
    raycaster.ray.direction.set(0, -1, 0);

    // 敌人生成(高度贴合地表)
    let enemies = [];
    class Enemy {
        constructor() {
            const enemyTextures = [
                './image/1.png',
                './image/2.png',
            ];
            const texture = new THREE.TextureLoader().load(enemyTextures[Math.floor(Math.random() * enemyTextures.length)]);
            const material = new THREE.SpriteMaterial({ map: texture });
            this.sprite = new THREE.Sprite(material);
            this.sprite.scale.set(2, 2, 1);

            // 随机选择地图边缘(0: 上, 1: 下, 2: 左, 3: 右)
            const edge = Math.floor(Math.random() * 4);
            const halfSize = terrainSize / 2; // terrainSize = 100, halfSize = 50

            switch (edge) {
                case 0: // 上边缘 (z = 50)
                    this.sprite.position.set(
                        Math.random() * terrainSize - halfSize, // x: [-50, 50]
                        10,                                     // y: 10
                        halfSize                                // z: 50
                    );
                    break;
                case 1: // 下边缘 (z = -50)
                    this.sprite.position.set(
                        Math.random() * terrainSize - halfSize, // x: [-50, 50]
                        10,                                     // y: 10
                        -halfSize                               // z: -50
                    );
                    break;
                case 2: // 左边缘 (x = -50)
                    this.sprite.position.set(
                        -halfSize,                              // x: -50
                        10,                                     // y: 10
                        Math.random() * terrainSize - halfSize  // z: [-50, 50]
                    );
                    break;
                case 3: // 右边缘 (x = 50)
                    this.sprite.position.set(
                        halfSize,                               // x: 50
                        10,                                     // y: 10
                        Math.random() * terrainSize - halfSize  // z: [-50, 50]
                    );
                    break;
            }

            scene.add(this.sprite);
            this.sprite.material.opacity = 0;
            gsap.to(this.sprite.material, { opacity: 1, duration: 1 });
        }

        update() {
            this.sprite.lookAt(camera.position);
            const direction = new THREE.Vector3().subVectors(camera.position, this.sprite.position).normalize();
            this.sprite.position.add(direction.multiplyScalar(0.05));

            // 贴合地表高度
            raycaster.ray.origin.copy(this.sprite.position);
            raycaster.ray.origin.y = 10; // 从上方检测
            const intersects = raycaster.intersectObject(terrain);
            if (intersects.length > 0) {
                this.sprite.position.y = intersects[0].point.y + 1; // 高度为地表+1
            }

            if (this.sprite.position.distanceTo(camera.position) < 1) {
                deductHealth(15);
                scene.remove(this.sprite);
                enemies = enemies.filter(e => e !== this);
            }
        }
    }

    setInterval(() => {
        if (enemies.length < 10) {
            const newEnemy = new Enemy();
            enemies.push(newEnemy);
        }
    }, 2000); // 每2秒生成一个敌人

    // 计时与得分
    let timeLeft = 150;
    let score = 0;
    const timerDisplay = document.createElement('div');
    timerDisplay.className = 'ui';
    timerDisplay.style.top = '10px';
    timerDisplay.style.left = '10px';
    timerDisplay.innerText = `时间: ${timeLeft} | 分数: ${score}`;
    document.body.appendChild(timerDisplay);

    setInterval(() => {
        if (isGamePaused) return; // 如果暂停,不更新计时器
        timeLeft--;
        score++;
        timerDisplay.innerText = `时间: ${timeLeft} | 分数: ${score}`;
        if (timeLeft <= 0) {
            endGame();
        }
    }, 1000);
    // 新增游戏结束函数
    function endGame() {
        isGamePaused = true;
        document.exitPointerLock();

        // 显示弹窗
        const modal = document.getElementById('gameOverModal');
        const finalScore = document.getElementById('finalScore');
        finalScore.innerText = `最终得分: ${score}`;
        modal.style.display = 'block';
    }
    // 新增重新开始函数
    function restartGame() {
        // 重置游戏状态
        isGamePaused = false;
        health = 100;
        bullets = maxBullets;
        timeLeft = 150;
        score = 0;
        enemies.forEach(enemy => scene.remove(enemy.sprite)); // 移除所有敌人
        enemies = [];

        // 更新UI
        healthDisplay.innerText = `生命值: ${health}`;
        bulletDisplay.innerText = `子弹: ${bullets}`;
        timerDisplay.innerText = `时间: ${timeLeft} | 分数: ${score}`;

        // 隐藏弹窗
        document.getElementById('gameOverModal').style.display = 'none';

        // 重新启动动画循环
        animate();
    }

    // 为重新开始按钮添加事件监听
    document.getElementById('restartButton').addEventListener('click', restartGame);

    // 天空贴图
    const skyboxLoader = new THREE.CubeTextureLoader();
    const skyboxTexture = skyboxLoader.load([
        'https://i.imgur.com/px.jpg', 'https://i.imgur.com/nx.jpg',
        'https://i.imgur.com/py.jpg', 'https://i.imgur.com/ny.jpg',
        'https://i.imgur.com/pz.jpg', 'https://i.imgur.com/nz.jpg'
    ]); // 替换为实际天空贴图URL
    scene.background = skyboxTexture;

    // 动画循环
    function animate() {
        if (isGamePaused) return; // 如果暂停则跳出循环
        requestAnimationFrame(animate);

        // WASD移动
        const speed = 0.1;
        const frontVector = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
        const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion);
        if (keys['w']) camera.position.add(frontVector.multiplyScalar(speed));
        if (keys['s']) camera.position.add(frontVector.multiplyScalar(-speed));
        if (keys['a']) camera.position.add(rightVector.multiplyScalar(-speed));
        if (keys['d']) camera.position.add(rightVector.multiplyScalar(speed));

        // 贴合地形高度
        raycaster.ray.origin.copy(camera.position);
        raycaster.ray.origin.y += 1.6;
        const intersects = raycaster.intersectObject(terrain);
        if (intersects.length > 0) {
            camera.position.y = intersects[0].point.y + 1.6;
        }

        // 更新敌人(仅在游戏未暂停时)
        if (!isGamePaused) {
            enemies.forEach(enemy => enemy.update());
        }

        renderer.render(scene, camera);
    }
    animate();

    // 自适应窗口大小
    window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    });
</script>
</body>
</html>