3D跑酷模拟器 - 完整实现
下面是一个功能丰富的3D跑酷模拟器HTML实现,包含角色动画、多种障碍物、特效和声音效果:
<!DOCTYPE html>
<html lang="zh">
<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;
font-family: 'Arial', sans-serif;
touch-action: none;
background-color: #000;
}
#game-container {
position: absolute;
width: 100%;
height: 100%;
}
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
#score-container {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 24px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
#score {
font-weight: bold;
color: #FFD700;
}
#high-score {
font-size: 16px;
color: #AAA;
}
#menu {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: rgba(0,0,0,0.7);
z-index: 20;
}
#menu h1 {
color: #FFD700;
font-size: 48px;
margin-bottom: 10px;
text-shadow: 0 0 10px rgba(255,215,0,0.7);
}
#menu p {
color: white;
font-size: 18px;
max-width: 500px;
text-align: center;
margin-bottom: 30px;
}
.btn {
padding: 15px 30px;
font-size: 20px;
background: linear-gradient(135deg, #FF8C00, #FF4500);
color: white;
border: none;
border-radius: 30px;
cursor: pointer;
pointer-events: auto;
box-shadow: 0 5px 15px rgba(255,140,0,0.4);
transition: all 0.3s;
margin: 10px;
min-width: 200px;
text-align: center;
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(255,140,0,0.6);
}
#game-over {
display: none;
}
#controls {
position: absolute;
bottom: 30px;
width: 100%;
display: flex;
justify-content: center;
gap: 30px;
}
.control-btn {
width: 80px;
height: 80px;
background-color: rgba(255,255,255,0.2);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
color: white;
pointer-events: auto;
user-select: none;
backdrop-filter: blur(5px);
border: 2px solid rgba(255,255,255,0.3);
}
#left-btn { margin-right: 20px; }
#right-btn { margin-left: 20px; }
#jump-btn {
background-color: rgba(255,100,100,0.3);
border-color: rgba(255,100,100,0.5);
}
#pause-btn {
position: absolute;
top: 20px;
right: 20px;
width: 50px;
height: 50px;
font-size: 20px;
display: none;
}
#powerup-bar {
position: absolute;
bottom: 120px;
left: 50%;
transform: translateX(-50%);
width: 200px;
height: 20px;
background-color: rgba(0,0,0,0.5);
border-radius: 10px;
overflow: hidden;
display: none;
}
#powerup-fill {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #00FF00, #00BFFF);
transition: width 0.3s;
}
#powerup-icon {
position: absolute;
bottom: 150px;
left: 50%;
transform: translateX(-50%);
font-size: 30px;
color: #00BFFF;
display: none;
}
</style>
</head>
<body>
<div id="game-container"></div>
<div id="ui">
<div id="score-container">
<div>分数: <span id="score">0</span></div>
<div id="high-score">最高分: 0</div>
</div>
<div id="controls">
<div class="control-btn" id="left-btn">←</div>
<div class="control-btn" id="jump-btn">↑</div>
<div class="control-btn" id="right-btn">→</div>
</div>
<div class="control-btn" id="pause-btn">⏸</div>
<div id="powerup-bar">
<div id="powerup-fill"></div>
</div>
<div id="powerup-icon">⚡</div>
</div>
<div id="menu">
<h1>极速跑酷</h1>
<p>穿越障碍,收集能量,创造最高分!</p>
<button class="btn" id="start-btn">开始游戏</button>
<button class="btn" id="tutorial-btn">游戏教程</button>
<div id="game-over" style="display:none;">
<h1 style="color:#FF5555;">游戏结束</h1>
<p>你的分数: <span id="final-score">0</span></p>
<button class="btn" id="restart-btn">再玩一次</button>
</div>
<div id="tutorial" style="display:none; color:white; max-width:600px; text-align:center;">
<h2>游戏控制</h2>
<p>← → 键或屏幕按钮控制左右移动</p>
<p>空格键或↑按钮跳跃</p>
<p>收集蓝色能量块可以获得加速能力</p>
<p>避开红色障碍物和墙壁</p>
<p>游戏速度会随时间增加</p>
<button class="btn" id="back-btn">返回</button>
</div>
</div>
<!-- 加载Three.js和附加组件 -->
<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/loaders/GLTFLoader.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
<script>
// 游戏状态
const GameState = {
MENU: 0,
PLAYING: 1,
PAUSED: 2,
GAME_OVER: 3,
TUTORIAL: 4
};
// 游戏变量
let scene, camera, renderer, mixer, clock;
let player, playerModel, animations = {};
let lanes = [-3, 0, 3];
let currentLane = 1;
let score = 0, highScore = 0;
let gameState = GameState.MENU;
let baseSpeed = 0.2, speedMultiplier = 1;
let obstacles = [], coins = [], powerups = [];
let isJumping = false, jumpHeight = 0, gravity = 0.02;
let lastObstacleTime = 0, obstacleInterval = 2000;
let lastCoinTime = 0, coinInterval = 3000;
let lastPowerupTime = 0, powerupInterval = 10000;
let powerupActive = false, powerupEndTime = 0, powerupDuration = 5000;
let keys = {};
let soundEnabled = true;
let sounds = {};
// 初始化游戏
async function init() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
scene.fog = new THREE.Fog(0x111122, 10, 50);
// 创建相机
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 15);
camera.lookAt(0, 0, 0);
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('game-container').appendChild(renderer.domElement);
// 添加光源
createLights();
// 创建环境
createEnvironment();
// 加载角色模型和动画
await loadCharacter();
// 创建UI事件
setupUI();
// 初始化声音
initSounds();
// 开始动画循环
clock = new THREE.Clock();
animate();
}
function createLights() {
// 环境光
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 7);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
scene.add(directionalLight);
// 聚光灯 (跟随玩家)
const spotLight = new THREE.SpotLight(0xffffff, 0.5, 0, Math.PI / 6, 0.5);
spotLight.position.set(0, 10, -5);
spotLight.target.position.set(0, 0, 0);
spotLight.castShadow = true;
scene.add(spotLight);
scene.add(spotLight.target);
}
function createEnvironment() {
// 地面
const groundGeometry = new THREE.PlaneGeometry(30, 1000);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x333333,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.5;
ground.receiveShadow = true;
scene.add(ground);
// 跑道标记
for (let i = -1; i <= 1; i++) {
const lineGeometry = new THREE.PlaneGeometry(0.1, 1000);
const lineMaterial = new THREE.MeshStandardMaterial({
color: 0xFFFFFF,
emissive: 0x888888,
emissiveIntensity: 0.5
});
const line = new THREE.Mesh(lineGeometry, lineMaterial);
line.rotation.x = -Math.PI / 2;
line.position.set(i * 3.33, -0.4, 0);
scene.add(line);
}
// 远处建筑
for (let i = 0; i < 20; i++) {
const height = Math.random() * 10 + 5;
const building = new THREE.Mesh(
new THREE.BoxGeometry(3 + Math.random() * 7, height, 3 + Math.random() * 7),
new THREE.MeshStandardMaterial({
color: 0x555577,
metalness: 0.3,
roughness: 0.7
})
);
building.position.set(
(Math.random() - 0.5) * 50,
height / 2 - 0.5,
-50 - Math.random() * 200
);
building.castShadow = true;
building.receiveShadow = true;
scene.add(building);
}
// 粒子效果 - 星空背景
const starGeometry = new THREE.BufferGeometry();
const starCount = 1000;
const positions = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
const i3 = i * 3;
positions[i3] = (Math.random() - 0.5) * 2000;
positions[i3 + 1] = (Math.random() - 0.5) * 2000;
positions[i3 + 2] = (Math.random() - 0.5) * 2000;
}
starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const starMaterial = new THREE.PointsMaterial({
color: 0xFFFFFF,
size: 0.5,
transparent: true,
opacity: 0.8
});
const stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);
}
async function loadCharacter() {
// 简单立方体作为临时角色
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0xFF5722 });
player = new THREE.Mesh(geometry, material);
player.position.set(lanes[currentLane], 0.5, 0);
player.castShadow = true;
scene.add(player);
// 实际项目中可以加载GLTF角色模型
// try {
// const loader = new THREE.GLTFLoader();
// const gltf = await loader.loadAsync('path/to/character.glb');
// playerModel = gltf.scene;
// playerModel.scale.set(0.5, 0.5, 0.5);
// playerModel.position.set(lanes[currentLane], 0, 0);
// scene.add(playerModel);
// mixer = new THREE.AnimationMixer(playerModel);
// animations = {
// run: mixer.clipAction(gltf.animations[0]),
// jump: mixer.clipAction(gltf.animations[1]),
// idle: mixer.clipAction(gltf.animations[2])
// };
// animations.run.play();
// } catch (error) {
// console.error("Failed to load character model:", error);
// // 使用立方体作为回退
// player = new THREE.Mesh(geometry, material);
// player.position.set(lanes[currentLane], 0.5, 0);
// scene.add(player);
// }
}
function initSounds() {
// 在实际项目中,可以加载音效文件
sounds = {
jump: new Audio('https://assets.mixkit.co/sfx/preview/mixkit-quick-jump-arcade-game-239.mp3'),
coin: new Audio('https://assets.mixkit.co/sfx/preview/mixkit-coin-win-notification-919.mp3'),
powerup: new Audio('https://assets.mixkit.co/sfx/preview/mixkit-powerup-electricity-2588.mp3'),
crash: new Audio('https://assets.mixkit.co/sfx/preview/mixkit-racing-collision-1569.mp3')
};
// 设置音量
Object.values(sounds).forEach(sound => {
sound.volume = 0.3;
});
}
function playSound(name) {
if (!soundEnabled) return;
const sound = sounds[name];
if (sound) {
sound.currentTime = 0;
sound.play().catch(e => console.log("Audio play failed:", e));
}
}
function setupUI() {
// 开始按钮
document.getElementById('start-btn').addEventListener('click', startGame);
document.getElementById('restart-btn').addEventListener('click', startGame);
// 教程按钮
document.getElementById('tutorial-btn').addEventListener('click', showTutorial);
document.getElementById('back-btn').addEventListener('click', hideTutorial);
// 暂停按钮
document.getElementById('pause-btn').addEventListener('click', togglePause);
// 控制按钮
document.getElementById('left-btn').addEventListener('touchstart', moveLeft);
document.getElementById('right-btn').addEventListener('touchstart', moveRight);
document.getElementById('jump-btn').addEventListener('touchstart', jump);
// 键盘控制
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
// 窗口大小调整
window.addEventListener('resize', onWindowResize);
}
function showTutorial() {
document.getElementById('tutorial').style.display = 'block';
document.getElementById('game-over').style.display = 'none';
document.querySelector('#menu h1').style.display = 'none';
document.querySelector('#menu p').style.display = 'none';
document.getElementById('start-btn').style.display = 'none';
document.getElementById('tutorial-btn').style.display = 'none';
}
function hideTutorial() {
document.getElementById('tutorial').style.display = 'none';
document.querySelector('#menu h1').style.display = 'block';
document.querySelector('#menu p').style.display = 'block';
document.getElementById('start-btn').style.display = 'block';
document.getElementById('tutorial-btn').style.display = 'block';
if (gameState === GameState.GAME_OVER) {
document.getElementById('game-over').style.display = 'block';
}
}
function startGame() {
// 重置游戏状态
gameState = GameState.PLAYING;
score = 0;
baseSpeed = 0.2;
speedMultiplier = 1;
currentLane = 1;
isJumping = false;
jumpHeight = 0;
powerupActive = false;
// 重置玩家位置
player.position.set(lanes[currentLane], 0.5, 0);
// 清除所有对象
obstacles.forEach(obj => scene.remove(obj));
coins.forEach(obj => scene.remove(obj));
powerups.forEach(obj => scene.remove(obj));
obstacles = [];
coins = [];
powerups = [];
// 更新UI
document.getElementById('menu').style.display = 'none';
document.getElementById('pause-btn').style.display = 'flex';
document.getElementById('powerup-bar').style.display = 'none';
document.getElementById('powerup-icon').style.display = 'none';
updateScore();
// 重置计时器
lastObstacleTime = Date.now();
lastCoinTime = Date.now();
lastPowerupTime = Date.now();
}
function gameOver() {
gameState = GameState.GAME_OVER;
playSound('crash');
// 更新最高分
if (score > highScore) {
highScore = score;
document.getElementById('high-score').textContent = `最高分: ${highScore}`;
document.getElementById('high-score').style.color = '#FFD700';
setTimeout(() => {
document.getElementById('high-score').style.color = '#AAA';
}, 2000);
}
// 显示游戏结束界面
document.getElementById('menu').style.display = 'flex';
document.getElementById('game-over').style.display = 'block';
document.getElementById('final-score').textContent = score;
document.getElementById('pause-btn').style.display = 'none';
}
function togglePause() {
if (gameState === GameState.PLAYING) {
gameState = GameState.PAUSED;
document.getElementById('pause-btn').textContent = '▶';
document.getElementById('menu').style.display = 'flex';
document.querySelector('#menu h1').textContent = '游戏暂停';
document.querySelector('#menu p').style.display = 'none';
document.getElementById('start-btn').style.display = 'none';
document.getElementById('tutorial-btn').style.display = 'none';
document.getElementById('game-over').style.display = 'none';
} else if (gameState === GameState.PAUSED) {
gameState = GameState.PLAYING;
document.getElementById('pause-btn').textContent = '⏸';
document.getElementById('menu').style.display = 'none';
}
}
function updateScore() {
document.getElementById('score').textContent = score;
// 分数动画
document.getElementById('score').style.transform = 'scale(1.2)';
setTimeout(() => {
document.getElementById('score').style.transform = 'scale(1)';
}, 200);
}
function createObstacle() {
const types = [
{ name: 'box', width: 2, height: 1, depth: 2, color: 0x8B4513, y: 0.5 },
{ name: 'wall', width: 1, height: 2, depth: 0.2, color: 0xAA5555, y: 1 },
{ name: 'barrier', width: 0.5, height: 0.5, depth: 0.5, color: 0xAA0000, y: 0.5, isCylinder: true }
];
const type = types[Math.floor(Math.random() * types.length)];
let obstacle;
if (type.isCylinder) {
obstacle = new THREE.Mesh(
new THREE.CylinderGeometry(type.width, type.width, type.height, 8),
new THREE.MeshStandardMaterial({ color: type.color })
);
} else {
obstacle = new THREE.Mesh(
new THREE.BoxGeometry(type.width, type.height, type.depth),
new THREE.MeshStandardMaterial({
color: type.color,
metalness: 0.3,
roughness: 0.7
})
);
}
const lane = Math.floor(Math.random() * lanes.length);
obstacle.position.set(lanes[lane], type.y, -20);
obstacle.castShadow = true;
obstacle.userData = { type: type.name, lane: lane };
scene.add(obstacle);
obstacles.push(obstacle);
// 随机间隔
obstacleInterval = 1000 + Math.random() * 1500;
}
function createCoin() {
const coin = new THREE.Mesh(
new THREE.CylinderGeometry(0.5, 0.5, 0.1, 32),
new THREE.MeshStandardMaterial({
color: 0xFFD700,
metalness: 0.9,
roughness: 0.1,
emissive: 0xFFD700,
emissiveIntensity: 0.3
})
);
const lane = Math.floor(Math.random() * lanes.length);
coin.position.set(lanes[lane], 1.5, -20);
coin.rotation.x = Math.PI / 2;
coin.castShadow = true;
coin.userData = { type: 'coin', lane: lane };
scene.add(coin);
coins.push(coin);
// 随机间隔
coinInterval = 2000 + Math.random() * 2000;
}
function createPowerup() {
const powerup = new THREE.Mesh(
new THREE.SphereGeometry(0.7, 16, 16),
new THREE.MeshStandardMaterial({
color: 0x00BFFF,
metalness: 0.5,
roughness: 0.2,
emissive: 0x00BFFF,
emissiveIntensity: 0.5
})
);
const lane = Math.floor(Math.random() * lanes.length);
powerup.position.set(lanes[lane], 1.2, -20);
powerup.castShadow = true;
powerup.userData = { type: 'powerup', lane: lane };
scene.add(powerup);
powerups.push(powerup);
// 随机间隔
powerupInterval = 8000 + Math.random() * 7000;
}
function moveLeft() {
if (gameState !== GameState.PLAYING) return;
currentLane = Math.max(0, currentLane - 1);
}
function moveRight() {
if (gameState !== GameState.PLAYING) return;
currentLane = Math.min(2, currentLane + 1);
}
function jump() {
if (gameState !== GameState.PLAYING || isJumping) return;
isJumping = true;
jumpVelocity = 0.15;
playSound('jump');
// 播放跳跃动画
// if (animations.jump) {
// animations.run.stop();
// animations.jump.reset().play();
// }
}
function updateJump() {
if (!isJumping) return;
jumpHeight += jumpVelocity;
jumpVelocity -= gravity;
if (jumpHeight <= 0) {
jumpHeight = 0;
isJumping = false;
// if (animations.run) animations.run.play();
}
player.position.y = 0.5 + jumpHeight;
}
function activatePowerup() {
powerupActive = true;
powerupEndTime = Date.now() + powerupDuration;
speedMultiplier = 1.5;
playSound('powerup');
// 显示UI
document.getElementById('powerup-bar').style.display = 'block';
document.getElementById('powerup-icon').style.display = 'block';
}
function updatePowerup() {
if (!powerupActive) return;
const remainingTime = powerupEndTime - Date.now();
const percent = Math.max(0, remainingTime / powerupDuration * 100);
document.getElementById('powerup-fill').style.width = `${percent}%`;
// 闪烁效果当快要结束时
if (remainingTime < 1000) {
document.getElementById('powerup-icon').style.opacity =
(Date.now() % 200 < 100) ? '1' : '0.5';
}
if (remainingTime <= 0) {
powerupActive = false;
speedMultiplier = 1;
document.getElementById('powerup-bar').style.display = 'none';
document.getElementById('powerup-icon').style.display = 'none';
}
}
function checkCollisions() {
const playerBox = new THREE.Box3().setFromObject(player);
// 检查障碍物碰撞
for (let i = 0; i < obstacles.length; i++) {
const obstacle = obstacles[i];
const obstacleBox = new THREE.Box3().setFromObject(obstacle);
if (playerBox.intersectsBox(obstacleBox)) {
gameOver();
return;
}
}
// 检查金币碰撞
for (let i = 0; i < coins.length; i++) {
const coin = coins[i];
const coinBox = new THREE.Box3().setFromObject(coin);
if (playerBox.intersectsBox(coinBox)) {
scene.remove(coin);
coins.splice(i, 1);
score += 5;
updateScore();
playSound('coin');
// 创建粒子效果
createCoinParticles(coin.position);
break;
}
}
// 检查能量块碰撞
for (let i = 0; i < powerups.length; i++) {
const powerup = powerups[i];
const powerupBox = new THREE.Box3().setFromObject(powerup);
if (playerBox.intersectsBox(powerupBox)) {
scene.remove(powerup);
powerups.splice(i, 1);
score += 10;
updateScore();
activatePowerup();
playSound('powerup');
// 创建粒子效果
createPowerupParticles(powerup.position);
break;
}
}
}
function createCoinParticles(position) {
const particleCount = 20;
const particles = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
positions[i3] = position.x + (Math.random() - 0.5) * 2;
positions[i3 + 1] = position.y + (Math.random() - 0.5) * 2;
positions[i3 + 2] = position.z + (Math.random() - 0.5) * 2;
colors[i3] = 1.0; // R
colors[i3 + 1] = 0.85; // G
colors[i3 + 2] = 0.0; // B
}
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particles.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const particleMaterial = new THREE.PointsMaterial({
size: 0.2,
vertexColors: true,
transparent: true,
opacity: 1,
blending: THREE.AdditiveBlending
});
const particleSystem = new THREE.Points(particles, particleMaterial);
scene.add(particleSystem);
// 动画粒子
const startTime = Date.now();
const duration = 1000;
function animateParticles() {
const elapsed = Date.now() - startTime;
const progress = elapsed / duration;
if (progress >= 1) {
scene.remove(particleSystem);
return;
}
// 更新粒子位置
const positions = particles.attributes.position.array;
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
positions[i3 + 1] += 0.05; // 向上移动
positions[i3 + 2] += 0.02; // 向前移动
}
particles.attributes.position.needsUpdate = true;
particleMaterial.opacity = 1 - progress;
requestAnimationFrame(animateParticles);
}
animateParticles();
}
function createPowerupParticles(position) {
const particleCount = 30;
const particles = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
positions[i3] = position.x + (Math.random() - 0.5) * 3;
positions[i3 + 1] = position.y + (Math.random() - 0.5) * 3;
positions[i3 + 2] = position.z + (Math.random() - 0.5) * 3;
colors[i3] = 0.0; // R
colors[i3 + 1] = 0.75; // G
colors[i3 + 2] = 1.0; // B
}
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particles.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const particleMaterial = new THREE.PointsMaterial({
size: 0.3,
vertexColors: true,
transparent: true,
opacity: 1,
blending: THREE.AdditiveBlending
});
const particleSystem = new THREE.Points(particles, particleMaterial);
scene.add(particleSystem);
// 动画粒子
const startTime = Date.now();
const duration = 1500;
function animateParticles() {
const elapsed = Date.now() - startTime;
const progress = elapsed / duration;
if (progress >= 1) {
scene.remove(particleSystem);
return;
}
// 更新粒子位置
const positions = particles.attributes.position.array;
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
positions[i3 + 1] += 0.03; // 向上移动
positions[i3 + 2] += 0.03; // 向前移动
}
particles.attributes.position.needsUpdate = true;
particleMaterial.opacity = 1 - progress;
requestAnimationFrame(animateParticles);
}
animateParticles();
}
// 动画循环
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
// 更新动画混合器
if (mixer) mixer.update(delta);
if (gameState === GameState.PLAYING) {
// 移动玩家到当前跑道
player.position.x += (lanes[currentLane] - player.position.x) * 0.2;
// 跳跃逻辑
updateJump();
// 更新能量块状态
updatePowerup();
// 移动障碍物
const speed = baseSpeed * speedMultiplier;
obstacles.forEach((obstacle, index) => {
obstacle.position.z += speed;
// 移除屏幕外的障碍物
if (obstacle.position.z > 5) {
scene.remove(obstacle);
obstacles.splice(index, 1);
}
});
// 移动金币
coins.forEach((coin, index) => {
coin.position.z += speed;
coin.rotation.y += 0.1;
// 移除屏幕外的金币
if (coin.position.z > 5) {
scene.remove(coin);
coins.splice(index, 1);
}
});
// 移动能量块
powerups.forEach((powerup, index) => {
powerup.position.z += speed;
powerup.rotation.y += 0.05;
powerup.rotation.x += 0.05;
// 移除屏幕外的能量块
if (powerup.position.z > 5) {
scene.remove(powerup);
powerups.splice(index, 1);
}
});
// 生成新障碍物
if (Date.now() - lastObstacleTime > obstacleInterval) {
createObstacle();
lastObstacleTime = Date.now();
}
// 生成新金币
if (Date.now() - lastCoinTime > coinInterval) {
createCoin();
lastCoinTime = Date.now();
}
// 生成新能量块
if (Date.now() - lastPowerupTime > powerupInterval) {
createPowerup();
lastPowerupTime = Date.now();
}
// 随着时间增加基础速度
baseSpeed = 0.2 + Math.min(0.3, score / 5000);
// 增加分数
score += 0.1 * speedMultiplier;
if (Math.floor(score) !== Math.floor(score - 0.1 * speedMultiplier)) {
updateScore();
}
// 检查碰撞
checkCollisions();
}
renderer.render(scene, camera);
}
function onKeyDown(event) {
keys[event.key] = true;
switch(event.key) {
case 'ArrowLeft':
moveLeft();
break;
case 'ArrowRight':
moveRight();
break;
case ' ':
case 'ArrowUp':
jump();
break;
case 'Enter':
if (gameState === GameState.MENU || gameState === GameState.GAME_OVER) {
startGame();
}
break;
case 'Escape':
if (gameState === GameState.PLAYING || gameState === GameState.PAUSED) {
togglePause();
}
break;
}
}
function onKeyUp(event) {
keys[event.key] = false;
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 启动游戏
init();
</script>
</body>
</html>
游戏特点
-
丰富的3D环境:
-
动态光照和阴影效果
-
星空背景和远处建筑
-
雾效增强深度感
-
-
多种游戏元素:
-
三种不同类型的障碍物(箱子、墙壁、路障)
-
可收集的金币和能量块
-
粒子特效增强视觉效果
-
-
角色控制:
-
平滑的左右移动和跳跃动画
-
键盘和触摸屏双重控制
-
能量块提供的加速效果
-
-
游戏机制:
-
随时间增加的游戏难度
-
碰撞检测系统
-
分数计算和最高分记录
-
-
完整的UI系统:
-
开始菜单和游戏教程
-
游戏暂停功能
-
能量块计时显示
-
响应式设计适应不同屏幕
-
如何扩展
-
添加更多角色模型和动画
-
增加更多类型的障碍物和收集物
-
实现关卡系统
-
添加背景音乐
-
增加更多特效(如角色受伤闪烁)
这个实现使用了Three.js库来创建3D效果,代码中包含了详细的注释,方便理解和修改。