HTML 3D跑酷模拟器 - 完整实现

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>

游戏特点

  1. 丰富的3D环境

    • 动态光照和阴影效果

    • 星空背景和远处建筑

    • 雾效增强深度感

  2. 多种游戏元素

    • 三种不同类型的障碍物(箱子、墙壁、路障)

    • 可收集的金币和能量块

    • 粒子特效增强视觉效果

  3. 角色控制

    • 平滑的左右移动和跳跃动画

    • 键盘和触摸屏双重控制

    • 能量块提供的加速效果

  4. 游戏机制

    • 随时间增加的游戏难度

    • 碰撞检测系统

    • 分数计算和最高分记录

  5. 完整的UI系统

    • 开始菜单和游戏教程

    • 游戏暂停功能

    • 能量块计时显示

    • 响应式设计适应不同屏幕

如何扩展

  1. 添加更多角色模型和动画

  2. 增加更多类型的障碍物和收集物

  3. 实现关卡系统

  4. 添加背景音乐

  5. 增加更多特效(如角色受伤闪烁)

这个实现使用了Three.js库来创建3D效果,代码中包含了详细的注释,方便理解和修改。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值