<!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>