Guarp af2ba6b1a3 添加缓存逻辑;
完成稿件管理逻辑;
新增博客编辑器;
新增博客;
新增请求测试小工具;
修改sweetheart样式;
打包用户头像组件;
新增导航栏隐藏功能;
新增3D打枪小游戏;
2025-03-13 23:52:31 +08:00

395 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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