代码解决方案:
<!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>
<!-- 使用 importmap 引入 Three.js -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<style>
/* ---------- 全局样式 ---------- */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
background: #0a0a1a;
overflow: hidden;
font-family: 'Segoe UI', Arial, sans-serif;
}
/* ---------- 游戏画布容器 ---------- */
#game-container {
width: 100vw;
height: 100vh;
display: block;
position: relative;
}
/* ---------- HUD 覆盖层 ---------- */
#hud {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
color: #ffffff;
font-family: 'Orbitron', /* 未来感字体 */
text-shadow: 0 0 5px rgba(0, 255, 255, 0.7);
}
#hud-top {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 20px;
align-items: center;
font-size: 1.5rem;
}
#hud-bottom {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 20px;
align-items: center;
font-size: 1.2rem;
}
.hud-box {
background: rgba(0, 0, 0, 0.5);
padding: 10px 20px;
border: 2px solid #00ffff;
border-radius: 5px;
text-align: center;
}
#health-bar {
width: 200px;
height: 20px;
background: #333;
border: 1px solid #00ffff;
border-radius: 10px;
overflow: hidden;
}
#health-fill {
height: 100%;
background: linear-gradient(to right, #ff0000, #ffff00, #00ff00);
width: 100%;
transition: width 0.3s ease-out;
}
#ammo-count {
font-size: 1.5rem;
color: #00ffff;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
width: 30px;
height: 30px;
transform: translate(-50%, -50%);
pointer-events: none;
border: 2px solid rgba(255, 255, 255, 0.7);
border-radius: 50%;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
#crosshair::before,
#crosshair::after {
content: '';
position: absolute;
background: rgba(255, 255, 255, 0.7);
}
#crosshair::before {
top: 50%;
left: 50%;
width: 2px;
height: 10px;
transform: translate(-50%, -50%);
}
#crosshair::after {
top: 50%;
left: 50%;
width: 10px;
height: 2px;
transform: translate(-50%, -50%);
}
/* ---------- 加载动画 ---------- */
#loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #0a0a1a;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
color: #00ffff;
font-family: 'Orbitron', sans-serif;
}
#loading-text {
font-size: 3rem;
margin-bottom: 20px;
text-shadow: 0 0 20px #00ffff;
animation: pulse 1.5s infinite alternate;
}
#loading-bar {
width: 300px;
height: 10px;
background: #333;
border-radius: 5px;
overflow: hidden;
}
#loading-progress {
height: 100%;
background: linear-gradient(to right, #00ffff, #ff00ff);
width: 0%;
transition: width 0.3s ease-in-out;
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 0.7;
}
100% {
transform: scale(1.1);
opacity: 1;
}
}
/* ---------- 游戏结束屏幕 ---------- */
#game-over-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
color: #ffffff;
font-family: 'Orbitron', sans-serif;
}
#game-over-title {
font-size: 4rem;
margin-bottom: 20px;
color: #ff0000;
text-shadow: 0 0 20px #ff0000;
}
#game-over-score {
font-size: 2rem;
margin-bottom: 30px;
color: #00ffff;
}
#restart-button {
padding: 15px 30px;
font-size: 1.5rem;
background: rgba(0, 255, 255, 0.2);
border: 2px solid #00ffff;
color: #ffffff;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
}
#restart-button:hover {
background: rgba(0, 255, 255, 0.5);
}
/* ---------- 控制提示 ---------- */
#controls-hint {
position: absolute;
bottom: 20px;
right: 20px;
color: rgba(255, 255, 255, 0.5);
font-size: 0.9rem;
pointer-events: none;
}
</style>
</head>
<body>
<!-- 游戏画布容器 -->
<div id="game-container">
<!-- HUD 覆盖层 -->
<div id="hud">
<div id="hud-top">
<div class="hud-box">
<div>得分: <span id="score">0</span></div>
</div>
<div class="hud-box">
<div>生命值</div>
<div id="health-bar">
<div id="health-fill"></div>
</div>
</div>
</div>
<div id="crosshair"></div>
<div id="hud-bottom">
<div class="hud-box">
<div>弹药: <span id="ammo-count">30 / 90</span></div>
</div>
</div>
<div id="controls-hint">
WASD 移动 | 鼠标瞄准射击 | R 装弹 | ESC 暂停
</div>
</div>
</div>
<!-- 加载屏幕 -->
<div id="loading-screen">
<div id="loading-text">加载中...</div>
<div id="loading-bar">
<div id="loading-progress"></div>
</div>
</div>
<!-- 游戏结束屏幕 -->
<div id="game-over-screen">
<div id="game-over-title">游戏结束</div>
<div id="game-over-score">最终得分: <span id="final-score">0</span></div>
<button id="restart-button">重新开始</button>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
// ---------- 全局变量 ----------
let scene, camera, renderer;
let tank, ground;
let bullets = [];
let enemies = [];
let particles = [];
let score = 0;
let health = 100;
let ammo = 30;
let maxAmmo = 30;
let totalAmmo = 90;
let gameRunning = true;
const keys = {
w: false,
a: false,
s: false,
d: false,
space: false,
r: false,
};
// ---------- 初始化 ----------
init();
animate();
function init() {
// 场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a1a);
scene.fog = new THREE.Fog(0x0a0a1a, 50, 200);
// 相机
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);
// 渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById('game-container').appendChild(renderer.domElement);
// 灯光
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(50, 50, 50);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
// 地面
const groundGeometry = new THREE.PlaneGeometry(200, 200);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x228b22,
roughness: 0.8,
metalness: 0.2,
});
ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// 坦克
createTank();
// 敌人
for (let i = 0; i < 5; i++) {
createEnemy();
}
// 事件监听
window.addEventListener('resize', onWindowResize);
window.addEventListener('keydown', onKeyDown);
);
window.addEventListener('keyup', onKeyUp);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('click', onMouseClick);
// 隐藏加载屏幕
setTimeout(() => {
document.getElementById('loading-screen').style.display = 'none';
}, 2000);
}
function createTank() {
// 简单坦克模型 (车体 + 炮塔)
const tankGroup = new THREE.Group();
// 车体
const bodyGeometry = new THREE.BoxGeometry(4, 1.5, 2);
const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0x556b2f });
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.castShadow = true;
body.receiveShadow = true;
tankGroup.add(body);
// 炮塔
const turretGeometry = new THREE.CylinderGeometry(1, 1.5, 1, 8);
const turretMaterial = new THREE.MeshStandardMaterial({ color: 0x556b2f });
const turret = new THREE.Mesh(turretGeometry, turretMaterial);
turret.position.y = 1.5;
turret.castShadow = true;
tankGroup.add(turret);
// 炮管
const barrelGeometry = new THREE.CylinderGeometry(0.2, 0.2, 3, 8);
const barrelMaterial = new THREE.MeshStandardMaterial({ color: 0x444444 });
const barrel = new THREE.Mesh(barrelGeometry, barrelMaterial);
barrel.rotation.z = Math.PI / 2;
barrel.position.set(1.5, 1.5, 0);
barrel.castShadow = true;
tankGroup.add(barrel);
tank = tankGroup;
tank.position.y = 1;
scene.add(tank);
}
function createBullet() {
const bulletGeometry = new THREE.SphereGeometry(0.2, 8, 8);
const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
bullet.position.copy(tank.position);
bullet.position.y += 1.5; // 炮塔高度
bullet.velocity = new THREE.Vector3(0, 0, -1);
bullet.velocity.applyQuaternion(tank.quaternion);
bullet.velocity.multiplyScalar(50); // 子弹速度
scene.add(bullet);
bullets.push(bullet);
}
function createEnemy() {
const enemyGroup = new THREE.Group();
// 敌人车体
const enemyBodyGeometry = new THREE.BoxGeometry(3, 1.5, 2);
const enemyBodyMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const enemyBody = new THREE.Mesh(enemyBodyGeometry, enemyBodyMaterial);
enemyBody.castShadow = true;
enemyBody.receiveShadow = true;
enemyGroup.add(enemyBody);
// 敌人炮塔
const enemyTurretGeometry = new THREE.CylinderGeometry(0.8, 1.2, 1, 8);
const enemyTurretMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const enemyTurret = new THREE.Mesh(enemyTurretGeometry, enemyTurretMaterial);
enemyTurret.position.y = 1.5;
enemyTurret.castShadow = true;
enemyGroup.add(enemyTurret);
// 随机位置
enemyGroup.position.set(
(Math.random() - 0.5) * 100,
1,
(Math.random() - 0.5) * 100
);
enemyGroup.lookAt(tank.position); // 朝向玩家
enemyGroup.userData = { health: 100 };
scene.add(enemyGroup);
enemies.push(enemyGroup);
}
function createExplosion(position) {
const particleCount = 50;
for (let i = 0; i < particleCount; i++) {
const particleGeometry = new THREE.SphereGeometry(0.1, 4, 4);
const particleMaterial = new THREE.MeshBasicMaterial({
color: new THREE.Color(
Math.random() * 0.5 + 0.5,
Math.random() * 0.3,
0
),
});
const particle = new THREE.Mesh(particleGeometry, particleMaterial);
particle.position.copy(position);
particle.velocity = new THREE.Vector3(
(Math.random() - 0.5) * 10,
Math.random() * 5 + 2,
(Math.random() - 0.5) * 10
);
scene.add(particle);
particles.push(particle);
}
}
function updateBullets() {
for (let i = bullets.length - 1; i >= 0; i--) {
const bullet = bullets[i];
bullet.position.add(bullet.velocity.clone().multiplyScalar(0.016)); // 60fps
// 检查碰撞
for (let j = enemies.length - 1; j >= 0; j--) {
const enemy = enemies[j];
if (
bullet.position.distanceTo(enemy.position) <
3 /* 敌人半径 */
) {
createExplosion(enemy.position);
scene.remove(enemy);
enemies.splice(j, 1);
scene.remove(bullet);
bullets.splice(i, 1);
score += 100;
document.getElementById('score').textContent = score;
break;
}
}
// 超出范围
if (bullet.position.length() > 100) {
scene.remove(bullet);
bullets.splice(i, 1);
}
}
}
function updateEnemies() {
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
enemy.lookAt(tank.position);
enemy.position.add(
new THREE.Vector3(0, 0, -0.05).applyQuaternion(enemy.quaternion)
);
// 简单 AI:每隔一段时间射击
if (Math.random() < 0.001) {
// 敌人射击逻辑可以在这里添加
}
// 检查与玩家碰撞
if (enemy.position.distanceTo(tank.position) < 5) {
health -= 1;
document.getElementById('health-fill').style.width = health + '%';
if (health <= 0) {
gameOver();
}
}
}
// 补充敌人
if (enemies.length < 5 && Math.random() < 0.01) {
createEnemy();
}
}
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const particle = particles[i];
particle.position.add(particle.velocity.clone().multiplyScalar(0.016));
particle.velocity.y -= 0.1; // 重力
particle.material.opacity -= 0.02;
if (particle.material.opacity <= 0) {
scene.remove(particle);
particles.splice(i, 1);
}
}
}
function updateTank() {
const speed = 0.5;
const rotSpeed = 0.05;
if (keys.w) {
tank.position.add(
new THREE.Vector3(0, 0, -speed).applyQuaternion(tank.quaternion)
);
}
if (keys.s) {
tank.position.add(
new THREE.Vector3(0, 0, speed).applyQuaternion(tank.quaternion)
);
}
if (keys.a) {
tank.rotation.y += rotSpeed;
}
if (keys.d) {
tank.rotation.y -= rotSpeed;
}
// 限制在地面
if (tank.position.x > 95) tank.position.x = 95;
if (tank.position.x < -95) tank.position.x = -95;
if (tank.position.z > 95) tank.position.z = 95;
if (tank.position.z < -95) tank.position.z = -95;
// 相机跟随
camera.position.x = tank.position.x;
camera.position.z = tank.position.z + 10;
camera.position.y = tank.position.y + 5;
camera.lookAt(tank.position);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onKeyDown(event) {
if (!gameRunning) return;
switch (event.key.toLowerCase()) {
case 'w':
keys.w = true;
break;
case 'a':
keys.a = true;
break;
case 's':
keys.s = true;
break;
case 'd':
keys.d = true;
break;
case ' ':
keys.space = true;
fireBullet();
break;
case 'r':
reload();
break;
}
}
function onKeyUp(event) {
switch (event.key.toLowerCase()) {
case 'w':
keys.w = false;
break;
case 'a':
keys.a = false;
break;
case 's':
keys.s = false;
break;
case 'd':
keys.d = false;
break;
case ' ':
keys.space = false;
break;
}
}
function onMouseMove(event) {
if (!gameRunning) return;
const mouseX = (event.clientX / window.innerWidth) * 2 - 1;
const mouseY = -(event.clientY / window.innerHeight) * 2 + 1;
// 简单的炮塔跟随鼠标 (这里简化处理)
tank.rotation.y = -mouseX * Math.PI;
}
function onMouseClick() {
if (!gameRunning) return;
fireBullet();
}
function fireBullet() {
if (ammo > 0) {
createBullet();
ammo--;
updateAmmoDisplay();
} else {
reload();
}
}
function reload() {
if (totalAmmo > 0) {
const needed = maxAmmo - ammo;
const toLoad = Math.min(needed, totalAmmo);
ammo += toLoad;
totalAmmo -= toLoad;
updateAmmoDisplay();
}
}
function updateAmmoDisplay() {
document.getElementById('ammo-count').textContent = `${ammo} / ${totalAmmo}`;
}
function gameOver() {
gameRunning = false;
document.getElementById('final-score').textContent = score;
document.getElementById('game-over-screen').style.display = 'flex';
}
function restartGame() {
// 重置游戏状态
score = 0;
health = 100;
ammo = 30;
totalAmmo = 90;
gameRunning = true;
document.getElementById('score').textContent = score;
document.getElementById('health-fill').style.width = '100%';
updateAmmoDisplay();
// 清除战场
for (const bullet of bullets) {
scene.remove(bullet);
}
bullets = [];
for (const enemy of enemies) {
scene.remove(enemy);
}
enemies = [];
for (const particle of particles) {
scene.remove(particle);
}
particles = [];
// 重置坦克位置
tank.position.set(0, 1, 0);
tank.rotation.set(0, 0, 0);
// 重新创建敌人
for (let i = 0; i < 5; i++) {
createEnemy();
}
document.getElementById('game-over-screen').style.display = 'none';
}
document.getElementById('restart-button').addEventListener('click', restartGame);
function animate() {
if (!gameRunning) return;
requestAnimationFrame(animate);
updateTank();
updateBullets();
updateEnemies();
updateParticles();
renderer.render(scene, camera);
}
</script>
</body>
</html>