test / index.html
Aprec's picture
Add 2 files
5f5a503 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D射击跟枪练习器</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Arial', sans-serif;
}
#container {
position: relative;
width: 100%;
height: 100vh;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 30px;
height: 30px;
pointer-events: none;
z-index: 100;
}
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 20px;
color: white;
background-color: rgba(0, 0, 0, 0.5);
z-index: 10;
}
#start-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 200;
color: white;
}
#instructions {
position: absolute;
bottom: 20px;
left: 20px;
color: white;
background-color: rgba(0, 0, 0, 0.5);
padding: 10px;
border-radius: 5px;
z-index: 10;
}
.target-hit {
position: absolute;
color: red;
font-size: 24px;
font-weight: bold;
animation: fadeOut 1s forwards;
}
@keyframes fadeOut {
to {
opacity: 0;
transform: translateY(-50px);
}
}
</style>
</head>
<body>
<div id="container">
<div id="crosshair">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" fill="none" stroke="white" stroke-width="2"/>
<line x1="50" y1="10" x2="50" y2="30" stroke="white" stroke-width="2"/>
<line x1="50" y1="70" x2="50" y2="90" stroke="white" stroke-width="2"/>
<line x1="10" y1="50" x2="30" y2="50" stroke="white" stroke-width="2"/>
<line x1="70" y1="50" x2="90" y2="50" stroke="white" stroke-width="2"/>
</svg>
</div>
<div id="ui">
<div class="flex justify-between">
<div>
<h2 class="text-xl font-bold">3D射击跟枪练习器</h2>
<p>提高你的Roblox射击跟枪技巧</p>
</div>
<div class="text-right">
<p class="text-lg">得分: <span id="score">0</span></p>
<p>命中率: <span id="accuracy">0</span>%</p>
<p>剩余时间: <span id="time">60</span></p>
<p>剩余敌人: <span id="enemies">5</span></p>
</div>
</div>
</div>
<div id="start-screen">
<h1 class="text-4xl font-bold mb-8">3D射击跟枪练习器</h1>
<p class="text-xl mb-8">练习你的跟枪技巧,提高Roblox游戏中的表现</p>
<div class="mb-8">
<label for="difficulty" class="block mb-2">选择难度:</label>
<select id="difficulty" class="bg-gray-700 text-white p-2 rounded">
<option value="easy">简单</option>
<option value="medium" selected>中等</option>
<option value="hard">困难</option>
<option value="extreme">极限</option>
</select>
</div>
<button id="start-btn" class="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-full text-xl transition duration-300">
开始练习
</button>
</div>
<div id="instructions">
<p>WASD: 移动</p>
<p>鼠标: 瞄准</p>
<p>左键: 射击</p>
<p>空格: 跳跃</p>
<p>Shift: 冲刺</p>
</div>
</div>
<script>
// 游戏变量
let scene, camera, renderer, controls;
let enemies = [];
let score = 0;
let shotsFired = 0;
let hits = 0;
let gameTime = 60;
let gameInterval;
let difficulty = 'medium';
let gameStarted = false;
let clock = new THREE.Clock();
let player, playerVelocity = new THREE.Vector3();
let playerDirection = new THREE.Vector3();
let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;
let canJump = true;
let isSprinting = false;
let pointerLockEnabled = false;
// 初始化Three.js场景
function init() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x88ccff);
scene.fog = new THREE.FogExp2(0x88ccff, 0.002);
// 创建相机
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.y = 1.6;
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').prepend(renderer.domElement);
// 添加光线
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// 添加地面
const groundGeometry = new THREE.PlaneGeometry(100, 100);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x3a5f0b,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// 添加障碍物
addObstacles();
// 创建玩家角色
createPlayer();
// 初始化指针锁定控制
controls = new THREE.PointerLockControls(camera, document.body);
// 添加事件监听器
window.addEventListener('resize', onWindowResize);
document.addEventListener('click', onMouseClick, false);
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
// 指针锁定状态变化事件
document.addEventListener('pointerlockchange', onPointerLockChange, false);
document.addEventListener('mozpointerlockchange', onPointerLockChange, false);
document.addEventListener('webkitpointerlockchange', onPointerLockChange, false);
// 开始按钮事件
document.getElementById('start-btn').addEventListener('click', function() {
// 先请求指针锁定
const element = document.body;
element.requestPointerLock = element.requestPointerLock ||
element.mozRequestPointerLock ||
element.webkitRequestPointerLock;
element.requestPointerLock();
});
// 难度选择
document.getElementById('difficulty').addEventListener('change', function(e) {
difficulty = e.target.value;
});
// 开始动画循环
animate();
}
// 指针锁定状态变化处理
function onPointerLockChange() {
pointerLockEnabled = (document.pointerLockElement === document.body ||
document.mozPointerLockElement === document.body ||
document.webkitPointerLockElement === document.body);
if (pointerLockEnabled && !gameStarted) {
// 指针锁定成功后开始游戏
startGame();
} else if (!pointerLockEnabled && gameStarted) {
// 指针锁定丢失时暂停游戏
pauseGame();
}
}
// 添加障碍物
function addObstacles() {
const boxGeometry = new THREE.BoxGeometry(5, 5, 5);
const boxMaterial = new THREE.MeshStandardMaterial({
color: 0x8b4513,
roughness: 0.7,
metalness: 0.3
});
// 添加几个箱子作为障碍物
for (let i = 0; i < 10; i++) {
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.x = Math.random() * 80 - 40;
box.position.z = Math.random() * 80 - 40;
box.position.y = 2.5;
box.castShadow = true;
box.receiveShadow = true;
scene.add(box);
}
// 添加一些墙
const wallGeometry = new THREE.BoxGeometry(1, 3, 20);
const wallMaterial = new THREE.MeshStandardMaterial({ color: 0x7f7f7f });
for (let i = 0; i < 5; i++) {
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.x = Math.random() * 60 - 30;
wall.position.z = Math.random() * 60 - 30;
wall.position.y = 1.5;
wall.castShadow = true;
wall.receiveShadow = true;
scene.add(wall);
}
}
// 创建玩家角色
function createPlayer() {
// 简单的玩家表示(只是一个立方体)
const geometry = new THREE.BoxGeometry(0.5, 1.8, 0.5);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });
player = new THREE.Mesh(geometry, material);
player.position.y = 1.8;
player.visible = false; // 我们不渲染玩家模型,因为它是第一人称视角
scene.add(player);
}
// 窗口大小调整
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 鼠标点击事件
function onMouseClick(event) {
if (!gameStarted || !pointerLockEnabled) return;
// 只在游戏开始且指针锁定状态下处理射击
if (event.button === 0) { // 左键点击
shotsFired++;
updateAccuracy();
// 设置光线投射器
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
// 检测与敌人的碰撞
const intersects = raycaster.intersectObjects(enemies);
if (intersects.length > 0) {
const enemy = intersects[0].object;
// 移除敌人
scene.remove(enemy);
enemies = enemies.filter(e => e !== enemy);
document.getElementById('enemies').textContent = enemies.length;
// 增加分数
score += 10;
document.getElementById('score').textContent = score;
// 增加命中数
hits++;
updateAccuracy();
// 显示命中效果
showHitEffect();
// 检查是否所有敌人都被消灭
if (enemies.length === 0) {
endGame(true);
}
}
}
}
// 显示命中效果
function showHitEffect() {
const hitEffect = document.createElement('div');
hitEffect.className = 'target-hit';
hitEffect.textContent = '+10';
hitEffect.style.left = '50%';
hitEffect.style.top = '50%';
document.getElementById('container').appendChild(hitEffect);
setTimeout(() => {
hitEffect.remove();
}, 1000);
}
// 更新命中率
function updateAccuracy() {
const accuracy = shotsFired > 0 ? Math.round((hits / shotsFired) * 100) : 0;
document.getElementById('accuracy').textContent = accuracy;
}
// 键盘按下事件
function onKeyDown(event) {
if (!gameStarted || !pointerLockEnabled) return;
switch(event.code) {
case 'KeyW': moveForward = true; break;
case 'KeyA': moveLeft = true; break;
case 'KeyS': moveBackward = true; break;
case 'KeyD': moveRight = true; break;
case 'Space':
if (canJump) {
playerVelocity.y += 15;
canJump = false;
}
break;
case 'ShiftLeft':
isSprinting = true;
break;
case 'Escape':
// ESC键退出指针锁定
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock;
document.exitPointerLock();
break;
}
}
// 键盘释放事件
function onKeyUp(event) {
if (!gameStarted || !pointerLockEnabled) return;
switch(event.code) {
case 'KeyW': moveForward = false; break;
case 'KeyA': moveLeft = false; break;
case 'KeyS': moveBackward = false; break;
case 'KeyD': moveRight = false; break;
case 'ShiftLeft':
isSprinting = false;
break;
}
}
// 创建敌人
function createEnemy() {
const enemyGeometry = new THREE.BoxGeometry(0.8, 1.8, 0.8);
const enemyMaterial = new THREE.MeshStandardMaterial({
color: 0xff0000,
roughness: 0.7,
metalness: 0.3
});
const enemy = new THREE.Mesh(enemyGeometry, enemyMaterial);
// 随机位置,但远离玩家
let x, z;
do {
x = (Math.random() * 60) - 30;
z = (Math.random() * 60) - 30;
} while (Math.sqrt(x*x + z*z) < 10); // 确保敌人不会生成在玩家附近
enemy.position.set(x, 0.9, z);
enemy.castShadow = true;
enemy.receiveShadow = true;
scene.add(enemy);
enemies.push(enemy);
// 添加敌人AI行为
animateEnemy(enemy);
}
// 敌人AI行为
function animateEnemy(enemy) {
let speed = 0.02;
let directionChangeTime = 0;
let currentDirection = new THREE.Vector3(
Math.random() * 2 - 1,
0,
Math.random() * 2 - 1
).normalize();
// 根据难度调整速度
switch(difficulty) {
case 'easy': speed = 0.015; break;
case 'medium': speed = 0.025; break;
case 'hard': speed = 0.035; break;
case 'extreme': speed = 0.045; break;
}
function move() {
if (!gameStarted) return;
// 定期改变方向
directionChangeTime--;
if (directionChangeTime <= 0) {
// 有时会朝玩家移动
if (Math.random() > 0.7) {
currentDirection = new THREE.Vector3().subVectors(
player.position,
enemy.position
).normalize();
} else {
currentDirection = new THREE.Vector3(
Math.random() * 2 - 1,
0,
Math.random() * 2 - 1
).normalize();
}
directionChangeTime = 100 + Math.random() * 100;
}
// 移动敌人
enemy.position.x += currentDirection.x * speed;
enemy.position.z += currentDirection.z * speed;
// 确保敌人不会离开地图
if (enemy.position.x < -45) enemy.position.x = -45;
if (enemy.position.x > 45) enemy.position.x = 45;
if (enemy.position.z < -45) enemy.position.z = -45;
if (enemy.position.z > 45) enemy.position.z = 45;
// 面向玩家
enemy.lookAt(player.position);
requestAnimationFrame(move);
}
move();
}
// 开始游戏
function startGame() {
// 重置游戏状态
score = 0;
shotsFired = 0;
hits = 0;
gameTime = 60;
gameStarted = true;
// 更新UI
document.getElementById('score').textContent = score;
document.getElementById('accuracy').textContent = '0';
document.getElementById('time').textContent = gameTime;
// 隐藏开始屏幕
document.getElementById('start-screen').style.display = 'none';
// 清除现有敌人
enemies.forEach(enemy => scene.remove(enemy));
enemies = [];
// 创建初始敌人
const enemyCount = difficulty === 'easy' ? 3 :
difficulty === 'medium' ? 5 :
difficulty === 'hard' ? 7 : 10;
for (let i = 0; i < enemyCount; i++) {
createEnemy();
}
document.getElementById('enemies').textContent = enemyCount;
// 重置玩家位置
player.position.set(0, 1.8, 0);
controls.getObject().position.set(0, 1.6, 0);
controls.getObject().rotation.set(0, 0, 0);
// 开始计时器
gameInterval = setInterval(() => {
gameTime--;
document.getElementById('time').textContent = gameTime;
if (gameTime <= 0) {
endGame(false);
}
}, 1000);
}
// 暂停游戏
function pauseGame() {
// 显示开始屏幕
const startScreen = document.getElementById('start-screen');
startScreen.style.display = 'flex';
// 更新结果
const accuracy = shotsFired > 0 ? Math.round((hits / shotsFired) * 100) : 0;
startScreen.innerHTML = `
<h1 class="text-4xl font-bold mb-8">游戏暂停</h1>
<div class="text-xl mb-8">
<p>当前得分: ${score}</p>
<p>命中率: ${accuracy}%</p>
<p>总射击次数: ${shotsFired}</p>
<p>命中次数: ${hits}</p>
<p>剩余敌人: ${enemies.length}</p>
</div>
<div class="mb-8">
<label for="difficulty" class="block mb-2">选择难度:</label>
<select id="difficulty" class="bg-gray-700 text-white p-2 rounded">
<option value="easy">简单</option>
<option value="medium" selected>中等</option>
<option value="hard">困难</option>
<option value="extreme">极限</option>
</select>
</div>
<button id="start-btn" class="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-full text-xl transition duration-300">
继续游戏
</button>
`;
// 重新添加事件监听器
document.getElementById('start-btn').addEventListener('click', function() {
// 重新请求指针锁定
const element = document.body;
element.requestPointerLock = element.requestPointerLock ||
element.mozRequestPointerLock ||
element.webkitRequestPointerLock;
element.requestPointerLock();
});
document.getElementById('difficulty').addEventListener('change', function(e) {
difficulty = e.target.value;
});
}
// 结束游戏
function endGame(win) {
gameStarted = false;
clearInterval(gameInterval);
// 解锁指针
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock;
document.exitPointerLock();
// 显示开始屏幕
const startScreen = document.getElementById('start-screen');
startScreen.style.display = 'flex';
// 更新结果
const accuracy = shotsFired > 0 ? Math.round((hits / shotsFired) * 100) : 0;
startScreen.innerHTML = `
<h1 class="text-4xl font-bold mb-8">${win ? '胜利!' : '游戏结束'}</h1>
<div class="text-xl mb-8">
<p>最终得分: ${score}</p>
<p>命中率: ${accuracy}%</p>
<p>总射击次数: ${shotsFired}</p>
<p>命中次数: ${hits}</p>
<p>剩余敌人: ${enemies.length}</p>
</div>
<div class="mb-8">
<label for="difficulty" class="block mb-2">选择难度:</label>
<select id="difficulty" class="bg-gray-700 text-white p-2 rounded">
<option value="easy">简单</option>
<option value="medium" selected>中等</option>
<option value="hard">困难</option>
<option value="extreme">极限</option>
</select>
</div>
<button id="start-btn" class="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-full text-xl transition duration-300">
再玩一次
</button>
`;
// 重新添加事件监听器
document.getElementById('start-btn').addEventListener('click', function() {
// 重新请求指针锁定
const element = document.body;
element.requestPointerLock = element.requestPointerLock ||
element.mozRequestPointerLock ||
element.webkitRequestPointerLock;
element.requestPointerLock();
});
document.getElementById('difficulty').addEventListener('change', function(e) {
difficulty = e.target.value;
});
}
// 动画循环
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (controls.isLocked && gameStarted) {
// 玩家移动
const speed = isSprinting ? 30 : 15;
playerVelocity.x -= playerVelocity.x * 10.0 * delta;
playerVelocity.z -= playerVelocity.z * 10.0 * delta;
playerDirection.z = Number(moveForward) - Number(moveBackward);
playerDirection.x = Number(moveRight) - Number(moveLeft);
playerDirection.normalize();
if (moveForward || moveBackward) playerVelocity.z -= playerDirection.z * speed * delta;
if (moveLeft || moveRight) playerVelocity.x -= playerDirection.x * speed * delta;
// 应用重力
playerVelocity.y -= 9.8 * delta;
// 更新玩家位置
controls.moveRight(-playerVelocity.x * delta);
controls.moveForward(-playerVelocity.z * delta);
controls.getObject().position.y += playerVelocity.y * delta;
// 确保玩家不会掉出地图
if (controls.getObject().position.y < 1.6) {
playerVelocity.y = 0;
controls.getObject().position.y = 1.6;
canJump = true;
}
// 确保玩家不会离开地图
const pos = controls.getObject().position;
if (pos.x < -45) pos.x = -45;
if (pos.x > 45) pos.x = 45;
if (pos.z < -45) pos.z = -45;
if (pos.z > 45) pos.z = 45;
// 更新玩家模型位置(用于敌人AI)
player.position.copy(controls.getObject().position);
player.position.y += 0.2; // 调整高度差
}
renderer.render(scene, camera);
}
// 初始化游戏
init();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=Aprec/test" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
</html>