未命名 uG5LCJedit icon

作者:
邓朝元
Fork(复制)
下载
嵌入
BUG反馈
index.html
md
README.md
现在支持上传本地图片了!
index.html
            
            <!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Minecraft Web - Mobile Edition</title>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body { 
            margin: 0; overflow: hidden; background-color: #1a1a2e; 
            touch-action: none; user-select: none; -webkit-user-select: none; 
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 
        }
        canvas { display: block; width: 100%; height: 100dvh; }
        
        /* UI 层 */
        #ui-layer { 
            position: absolute; top: 0; left: 0; width: 100%; height: 100%; 
            pointer-events: none; z-index: 10;
        }
        
        /* 准星 */
        #crosshair {
            position: absolute; top: 50%; left: 50%; width: 20px; height: 20px;
            transform: translate(-50%, -50%); pointer-events: none;
            z-index: 20;
        }
        #crosshair::before, #crosshair::after {
            content: ''; position: absolute; background: rgba(255,255,255,0.9);
            border-radius: 2px;
        }
        #crosshair::before {
            width: 20px; height: 2px; top: 9px; left: 0;
        }
        #crosshair::after {
            width: 2px; height: 20px; top: 0; left: 9px;
        }

        /* 横屏提示 */
        #orientation-warning {
            display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); 
            color: white; z-index: 100;
            justify-content: center; align-items: center; flex-direction: column; 
            text-align: center; padding: 20px;
        }
        @media screen and (orientation: portrait) {
            #orientation-warning { display: flex; }
        }
        #orientation-warning h1 { font-size: 24px; margin-bottom: 10px; }
        #orientation-warning p { font-size: 14px; opacity: 0.8; margin-bottom: 30px; }
        #orientation-warning .icon { font-size: 60px; animation: rotate 2s infinite; }
        @keyframes rotate { 0%,100%{transform:rotate(0)}50%{transform:rotate(90deg)} }

        /* 移动端控制区 */
        .mobile-control { pointer-events: auto; position: absolute; }
        
        /* 左侧摇杆区 */
        #joystick-zone {
            bottom: 80px; left: 30px; width: 110px; height: 110px;
            background: rgba(255, 255, 255, 0.12); border-radius: 50%;
            border: 2px solid rgba(255, 255, 255, 0.25);
            display: none;
            backdrop-filter: blur(8px);
        }
        #joystick-knob {
            position: absolute; top: 50%; left: 50%; width: 45px; height: 45px;
            background: rgba(255, 255, 255, 0.4); border-radius: 50%;
            transform: translate(-50%, -50%);
            border: 2px solid rgba(255,255,255,0.5);
            transition: background 0.1s;
        }
        #joystick-knob:active { background: rgba(255, 255, 255, 0.6); }

        /* 右侧按钮区 */
        #action-buttons {
            bottom: 60px; right: 25px; display: none; flex-direction: column; gap: 12px;
        }
        .btn {
            width: 55px; height: 55px; border-radius: 50%;
            background: rgba(30, 30, 50, 0.7); border: 2px solid rgba(255,255,255,0.4);
            color: white; font-weight: bold; font-size: 11px;
            display: flex; justify-content: center; align-items: center;
            backdrop-filter: blur(6px);
            transition: all 0.1s;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
        }
        .btn:active { 
            background: rgba(255, 255, 255, 0.25); 
            transform: scale(0.92); 
            border-color: rgba(255,255,255,0.8);
        }
        .btn-jump { background: rgba(80, 100, 255, 0.6); }
        .btn-break { background: rgba(255, 80, 80, 0.6); }
        .btn-place { background: rgba(80, 255, 120, 0.6); }

        /* 物品栏 */
        #hotbar {
            position: absolute; bottom: 15px; left: 50%; transform: translateX(-50%);
            display: flex; gap: 4px; background: rgba(20,20,35,0.85); 
            padding: 6px 8px; border-radius: 10px; pointer-events: auto;
            border: 1px solid rgba(255,255,255,0.2);
            backdrop-filter: blur(10px);
        }
        .slot {
            width: 38px; height: 38px; border: 2px solid #444; 
            border-radius: 6px; background-size: cover; background-position: center;
            image-rendering: pixelated; cursor: pointer;
            transition: all 0.15s;
        }
        .slot:hover { border-color: #888; transform: scale(1.05); }
        .slot.active { 
            border-color: #fff; transform: scale(1.12); 
            box-shadow: 0 0 0 2px rgba(255,255,255,0.4), 0 4px 12px rgba(0,0,0,0.4); 
            z-index: 2;
        }

        /* 说明文字 */
        #info {
            position: absolute; top: 12px; left: 12px; color: rgba(255,255,255,0.85);
            font-size: 11px; pointer-events: none; text-shadow: 0 1px 3px rgba(0,0,0,0.8);
            background: rgba(20,20,35,0.7); padding: 6px 10px; border-radius: 6px;
            border: 1px solid rgba(255,255,255,0.15);
            max-width: 280px; line-height: 1.4;
        }
        
        /* 加载提示 */
        #loading {
            position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
            color: white; font-size: 16px; text-align: center;
            background: rgba(30,30,50,0.9); padding: 20px 30px; border-radius: 12px;
            border: 1px solid rgba(255,255,255,0.2); z-index: 50;
        }
        #loading.hidden { display: none; }
    </style>
</head>
<body>
    <!-- 3D 容器 -->
    <div id="container"></div>
    <div id="loading">🌍 正在生成世界...</div>

    <!-- UI 层 -->
    <div id="ui-layer">
        <div id="crosshair"></div>
        <div id="info">
            🎮 <strong>操作指南</strong><br>
            📱 左摇杆:移动角色 | 右屏滑动:转动视角<br>
            🔘 按钮:跳跃 / 破坏 / 放置方块
        </div>
        
        <!-- 移动端控件 -->
        <div id="joystick-zone" class="mobile-control">
            <div id="joystick-knob"></div>
        </div>
        
        <div id="action-buttons" class="mobile-control">
            <div class="btn btn-jump" id="btn-jump">⬆️</div>
            <div class="btn btn-break" id="btn-break">🔨</div>
            <div class="btn btn-place" id="btn-place">🧱</div>
        </div>

        <div id="hotbar"></div>
    </div>

    <!-- 横屏提示 -->
    <div id="orientation-warning">
        <div class="icon">📱</div>
        <h1>请旋转手机</h1>
        <p>横屏模式可获得最佳游戏体验</p>
        <div style="font-size:40px">↹</div>
    </div>

    <!-- Three.js 导入 (修复了URL空格问题) -->
    <script type="importmap">
    {
        "imports": {
            "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
            "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
        }
    }
    </script>

    <script type="module">
        import * as THREE from 'three';

        // ========== 配置 ==========
        const CONFIG = {
            WORLD_SIZE: 24,
            BLOCK_SIZE: 1,
            PLAYER_HEIGHT: 1.7,
            PLAYER_RADIUS: 0.35,
            MOVE_SPEED: 8,
            JUMP_FORCE: 10,
            GRAVITY: 25,
            INTERACT_DISTANCE: 5,
            FOG_COLOR: 0x87ceeb,
            SKY_COLOR: 0x87ceeb
        };

        // ========== 程序化纹理生成 ==========
        function createBlockTexture(baseColor, pattern = 'default') {
            const canvas = document.createElement('canvas');
            canvas.width = 64; canvas.height = 64;
            const ctx = canvas.getContext('2d');
            
            // 基础填充
            ctx.fillStyle = baseColor;
            ctx.fillRect(0, 0, 64, 64);
            
            // 添加噪点/纹理
            for(let i = 0; i < 300; i++) {
                const alpha = Math.random() * 0.2;
                ctx.fillStyle = Math.random() > 0.5 
                    ? `rgba(0,0,0,${alpha})` 
                    : `rgba(255,255,255,${alpha * 0.6})`;
                ctx.fillRect(
                    Math.random() * 64, 
                    Math.random() * 64, 
                    2 + Math.random() * 2, 
                    2 + Math.random() * 2
                );
            }
            
            // 边框效果
            ctx.strokeStyle = 'rgba(0,0,0,0.15)';
            ctx.lineWidth = 3;
            ctx.strokeRect(1, 1, 62, 62);
            
            const texture = new THREE.CanvasTexture(canvas);
            texture.magFilter = THREE.NearestFilter;
            texture.minFilter = THREE.NearestFilter;
            texture.generateMipmaps = false;
            return texture;
        }

        // ========== 材质库 ==========
        const MATERIALS = {
            grass: new THREE.MeshLambertMaterial({ 
                map: createBlockTexture('#5a9c44'),
                color: 0x5a9c44
            }),
            dirt: new THREE.MeshLambertMaterial({ 
                map: createBlockTexture('#6b4423'),
                color: 0x6b4423
            }),
            stone: new THREE.MeshLambertMaterial({ 
                map: createBlockTexture('#7a7a7a'),
                color: 0x7a7a7a
            }),
            wood: new THREE.MeshLambertMaterial({ 
                map: createBlockTexture('#8b6914'),
                color: 0x8b6914
            }),
            brick: new THREE.MeshLambertMaterial({ 
                map: createBlockTexture('#a52a2a'),
                color: 0xa52a2a
            }),
            sand: new THREE.MeshLambertMaterial({ 
                map: createBlockTexture('#e6c96c'),
                color: 0xe6c96c
            }),
            leaves: new THREE.MeshLambertMaterial({ 
                map: createBlockTexture('#3a6b2e'),
                color: 0x3a6b2e,
                transparent: true,
                opacity: 0.9
            })
        };
        const BLOCK_TYPES = Object.keys(MATERIALS);

        // ========== 场景初始化 ==========
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(CONFIG.SKY_COLOR);
        scene.fog = new THREE.FogExp2(CONFIG.FOG_COLOR, 0.018);

        const camera = new THREE.PerspectiveCamera(
            75, 
            window.innerWidth / window.innerHeight, 
            0.1, 
            200
        );
        camera.position.set(0, CONFIG.PLAYER_HEIGHT, 0);
        camera.rotation.order = 'YXZ';

        const renderer = new THREE.WebGLRenderer({ 
            antialias: false, 
            powerPreference: "high-performance",
            alpha: false
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        document.getElementById('container').appendChild(renderer.domElement);

        // ========== 光照 ==========
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
        scene.add(ambientLight);

        const dirLight = new THREE.DirectionalLight(0xffffff, 0.7);
        dirLight.position.set(30, 50, 30);
        dirLight.castShadow = true;
        dirLight.shadow.mapSize.set(1024, 1024);
        dirLight.shadow.camera.near = 1;
        dirLight.shadow.camera.far = 100;
        dirLight.shadow.camera.left = -40;
        dirLight.shadow.camera.right = 40;
        dirLight.shadow.camera.top = 40;
        dirLight.shadow.camera.bottom = -40;
        scene.add(dirLight);

        // ========== 世界生成 ==========
        const blocks = [];
        const blockGeometry = new THREE.BoxGeometry(
            CONFIG.BLOCK_SIZE, 
            CONFIG.BLOCK_SIZE, 
            CONFIG.BLOCK_SIZE
        );

        function createBlock(x, y, z, materialKey) {
            const material = MATERIALS[materialKey];
            const mesh = new THREE.Mesh(blockGeometry, material);
            mesh.position.set(x, y, z);
            mesh.castShadow = true;
            mesh.receiveShadow = true;
            mesh.userData = { type: materialKey, x, y, z };
            scene.add(mesh);
            blocks.push(mesh);
            return mesh;
        }

        function generateTerrain() {
            const size = CONFIG.WORLD_SIZE;
            const half = size / 2;
            
            for (let x = -half; x < half; x++) {
                for (let z = -half; z < half; z++) {
                    // 简单的高度图生成
                    const height = Math.floor(
                        Math.sin(x * 0.3) * 1.5 + 
                        Math.cos(z * 0.3) * 1.5 + 
                        Math.sin((x + z) * 0.2) * 0.8
                    );
                    
                    // 生成地层
                    for (let y = -3; y <= height; y++) {
                        let blockType = 'stone';
                        if (y === height) {
                            blockType = height < -1 ? 'sand' : 'grass';
                        } else if (y > height - 2) {
                            blockType = 'dirt';
                        }
                        createBlock(x, y, z, blockType);
                    }
                    
                    // 随机生成树木
                    if (height >= 0 && Math.random() < 0.03) {
                        generateTree(x, height + 1, z);
                    }
                }
            }
        }

        function generateTree(x, y, z) {
            // 树干
            for (let h = 0; h < 4; h++) {
                createBlock(x, y + h, z, 'wood');
            }
            // 树叶
            for (let dx = -2; dx <= 2; dx++) {
                for (let dz = -2; dz <= 2; dz++) {
                    for (let dy = 2; dy <= 4; dy++) {
                        if (Math.abs(dx) + Math.abs(dz) + Math.abs(dy - 3) < 4) {
                            if (Math.random() > 0.2) {
                                createBlock(x + dx, y + dy, z + dz, 'leaves');
                            }
                        }
                    }
                }
            }
        }

        // ========== 玩家手臂 (视觉效果) ==========
        const armGroup = new THREE.Group();
        const armMesh = new THREE.Mesh(
            new THREE.BoxGeometry(0.15, 0.5, 0.15),
            new THREE.MeshLambertMaterial({ color: 0xffccaa })
        );
        armMesh.position.set(0.25, -0.5, -0.35);
        armGroup.add(armMesh);
        camera.add(armGroup);
        scene.add(camera);

        // ========== 输入状态 ==========
        const input = {
            move: { x: 0, z: 0 },
            jump: false,
            yaw: 0,
            pitch: 0
        };

        const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

        // ========== 物理系统 ==========
        const player = {
            position: new THREE.Vector3(0, 10, 0),
            velocity: new THREE.Vector3(0, 0, 0),
            onGround: false,
            box: new THREE.Box3()
        };

        function getPlayerBox(pos) {
            return new THREE.Box3(
                new THREE.Vector3(pos.x - CONFIG.PLAYER_RADIUS, pos.y, pos.z - CONFIG.PLAYER_RADIUS),
                new THREE.Vector3(pos.x + CONFIG.PLAYER_RADIUS, pos.y + CONFIG.PLAYER_HEIGHT, pos.z + CONFIG.PLAYER_RADIUS)
            );
        }

        function checkCollision(position) {
            const playerBox = getPlayerBox(position);
            for (const block of blocks) {
                const blockBox = new THREE.Box3().setFromObject(block);
                if (playerBox.intersectsBox(blockBox)) {
                    return true;
                }
            }
            return false;
        }

        // ========== 交互系统 (射线检测) ==========
        const raycaster = new THREE.Raycaster();
        const highlightMesh = new THREE.Mesh(
            new THREE.BoxGeometry(1.02, 1.02, 1.02),
            new THREE.MeshBasicMaterial({ 
                color: 0xffffff, 
                wireframe: true, 
                transparent: true, 
                opacity: 0.4 
            })
        );
        highlightMesh.visible = false;
        scene.add(highlightMesh);

        let selectedBlockIndex = 0;

        function handleInteract(isPlace) {
            raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
            const intersects = raycaster.intersectObjects(blocks);
            
            if (intersects.length === 0 || intersects[0].distance > CONFIG.INTERACT_DISTANCE) {
                return;
            }
            
            const hit = intersects[0];
            
            if (!isPlace) {
                // 破坏方块
                scene.remove(hit.object);
                const idx = blocks.indexOf(hit.object);
                if (idx > -1) blocks.splice(idx, 1);
            } else {
                // 放置方块
                const pos = hit.point.clone().add(hit.face.normal.multiplyScalar(0.5)).floor();
                
                // 防止放置在玩家体内
                const playerPos = player.position.clone();
                playerPos.y -= CONFIG.PLAYER_HEIGHT / 2;
                
                const dist = playerPos.distanceTo(
                    new THREE.Vector3(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5)
                );
                
                if (dist < 1.2) return;
                
                // 检查是否已存在方块
                const exists = blocks.some(b => 
                    Math.abs(b.position.x - (pos.x + 0.5)) < 0.1 &&
                    Math.abs(b.position.y - (pos.y + 0.5)) < 0.1 &&
                    Math.abs(b.position.z - (pos.z + 0.5)) < 0.1
                );
                
                if (!exists) {
                    createBlock(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, BLOCK_TYPES[selectedBlockIndex]);
                }
            }
        }

        // ========== 物品栏 ==========
        const hotbar = document.getElementById('hotbar');
        BLOCK_TYPES.forEach((type, index) => {
            const slot = document.createElement('div');
            slot.className = 'slot' + (index === 0 ? ' active' : '');
            // 使用 canvas 生成预览图
            const canvas = MATERIALS[type].map?.image;
            if (canvas) {
                slot.style.backgroundImage = `url(${canvas.toDataURL()})`;
            } else {
                slot.style.backgroundColor = MATERIALS[type].color?.getHexString 
                    ? '#' + MATERIALS[type].color.getHexString() 
                    : '#888';
            }
            slot.addEventListener('click', (e) => {
                e.stopPropagation();
                selectedBlockIndex = index;
                document.querySelectorAll('.slot').forEach(s => s.classList.remove('active'));
                slot.classList.add('active');
            });
            hotbar.appendChild(slot);
        });

        // ========== 移动端控制 ==========
        function setupMobileControls() {
            if (!isMobile) return;
            
            document.getElementById('joystick-zone').style.display = 'block';
            document.getElementById('action-buttons').style.display = 'flex';
            
            // --- 虚拟摇杆 ---
            const joystickZone = document.getElementById('joystick-zone');
            const joystickKnob = document.getElementById('joystick-knob');
            let joyTouchId = null;
            let joyCenter = { x: 0, y: 0 };
            const joyMaxDist = 32;
            
            joystickZone.addEventListener('touchstart', (e) => {
                e.preventDefault();
                const touch = e.changedTouches[0];
                joyTouchId = touch.identifier;
                const rect = joystickZone.getBoundingClientRect();
                joyCenter = { 
                    x: rect.left + rect.width / 2, 
                    y: rect.top + rect.height / 2 
                };
                updateJoystick(touch.clientX, touch.clientY);
            }, { passive: false });
            
            joystickZone.addEventListener('touchmove', (e) => {
                e.preventDefault();
                for (const touch of e.changedTouches) {
                    if (touch.identifier === joyTouchId) {
                        updateJoystick(touch.clientX, touch.clientY);
                        break;
                    }
                }
            }, { passive: false });
            
            function endJoystick(e) {
                for (const touch of e.changedTouches) {
                    if (touch.identifier === joyTouchId) {
                        joyTouchId = null;
                        input.move.x = 0;
                        input.move.z = 0;
                        joystickKnob.style.transform = 'translate(-50%, -50%)';
                        break;
                    }
                }
            }
            joystickZone.addEventListener('touchend', endJoystick);
            joystickZone.addEventListener('touchcancel', endJoystick);
            
            function updateJoystick(x, y) {
                let dx = x - joyCenter.x;
                let dy = y - joyCenter.y;
                const dist = Math.sqrt(dx * dx + dy * dy);
                
                if (dist > joyMaxDist) {
                    const ratio = joyMaxDist / dist;
                    dx *= ratio;
                    dy *= ratio;
                }
                
                joystickKnob.style.transform = 
                    `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`;
                
                // 映射到移动输入 (-1 ~ 1)
                input.move.x = dx / joyMaxDist;
                input.move.z = -dy / joyMaxDist; // Y轴反向
            }
            
            // --- 右侧屏幕滑动看视角 ---
            let lookTouchId = null;
            let lastLook = { x: 0, y: 0 };
            
            document.addEventListener('touchstart', (e) => {
                // 忽略控件区域
                if (e.target.closest('.mobile-control') || e.target.closest('#hotbar')) {
                    return;
                }
                
                const touch = e.changedTouches[0];
                // 只在右半屏触发视角
                if (touch.clientX > window.innerWidth * 0.5) {
                    lookTouchId = touch.identifier;
                    lastLook = { x: touch.clientX, y: touch.clientY };
                }
            }, { passive: false });
            
            document.addEventListener('touchmove', (e) => {
                if (lookTouchId === null) return;
                
                for (const touch of e.changedTouches) {
                    if (touch.identifier === lookTouchId) {
                        const dx = touch.clientX - lastLook.x;
                        const dy = touch.clientY - lastLook.y;
                        
                        input.yaw -= dx * 0.004;
                        input.pitch -= dy * 0.004;
                        input.pitch = Math.max(-Math.PI/2 + 0.1, Math.min(Math.PI/2 - 0.1, input.pitch));
                        
                        lastLook = { x: touch.clientX, y: touch.clientY };
                        break;
                    }
                }
            }, { passive: false });
            
            document.addEventListener('touchend', (e) => {
                for (const touch of e.changedTouches) {
                    if (touch.identifier === lookTouchId) {
                        lookTouchId = null;
                        break;
                    }
                }
            });
            
            // --- 按钮绑定 ---
            document.getElementById('btn-jump').addEventListener('touchstart', (e) => {
                e.preventDefault();
                if (player.onGround) {
                    player.velocity.y = CONFIG.JUMP_FORCE;
                    player.onGround = false;
                }
            });
            
            document.getElementById('btn-break').addEventListener('touchstart', (e) => {
                e.preventDefault();
                handleInteract(false);
            });
            
            document.getElementById('btn-place').addEventListener('touchstart', (e) => {
                e.preventDefault();
                handleInteract(true);
            });
        }

        // ========== PC端控制 (备用) ==========
        function setupDesktopControls() {
            if (isMobile) return;
            
            const keys = {};
            document.addEventListener('keydown', (e) => {
                keys[e.code] = true;
                if (['Space', 'ArrowUp', 'ArrowDown'].includes(e.code)) {
                    e.preventDefault();
                }
            });
            document.addEventListener('keyup', (e) => {
                keys[e.code] = false;
            });
            
            // 鼠标视角
            let isLocked = false;
            document.addEventListener('click', () => {
                if (!isLocked && document.body.requestPointerLock) {
                    document.body.requestPointerLock();
                }
            });
            
            document.addEventListener('pointerlockchange', () => {
                isLocked = document.pointerLockElement === document.body;
            });
            
            document.addEventListener('mousemove', (e) => {
                if (isLocked) {
                    input.yaw -= e.movementX * 0.002;
                    input.pitch -= e.movementY * 0.002;
                    input.pitch = Math.max(-Math.PI/2 + 0.1, Math.min(Math.PI/2 - 0.1, input.pitch));
                }
            });
            
            // 鼠标交互
            document.addEventListener('mousedown', (e) => {
                if (e.button === 0) handleInteract(false); // 左键破坏
                if (e.button === 2) handleInteract(true);  // 右键放置
            });
            document.addEventListener('contextmenu', (e) => e.preventDefault());
            
            // 键盘输入处理(在动画循环中读取)
            window.updateKeyboardInput = () => {
                input.move.z = (keys['KeyW'] ? 1 : 0) - (keys['KeyS'] ? 1 : 0);
                input.move.x = (keys['KeyD'] ? 1 : 0) - (keys['KeyA'] ? 1 : 0);
                
                if (keys['Space'] && player.onGround) {
                    player.velocity.y = CONFIG.JUMP_FORCE;
                    player.onGround = false;
                    keys['Space'] = false;
                }
                
                // 物品栏切换
                for (let i = 1; i <= 6; i++) {
                    if (keys[`Digit${i}`]) {
                        selectedBlockIndex = i - 1;
                        document.querySelectorAll('.slot').forEach((s, idx) => {
                            s.classList.toggle('active', idx === selectedBlockIndex);
                        });
                        keys[`Digit${i}`] = false;
                        break;
                    }
                }
            };
        }

        // ========== 主循环 ==========
        let lastTime = performance.now();
        
        function animate() {
            requestAnimationFrame(animate);
            
            const now = performance.now();
            const delta = Math.min((now - lastTime) / 1000, 0.1);
            lastTime = now;
            
            // 更新键盘输入 (PC)
            if (window.updateKeyboardInput) {
                window.updateKeyboardInput();
            }
            
            // 更新相机旋转
            camera.rotation.set(input.pitch, input.yaw, 0, 'YXZ');
            
            // 计算移动方向
            if (input.move.x !== 0 || input.move.z !== 0) {
                const forward = new THREE.Vector3(0, 0, -1);
                forward.applyAxisAngle(new THREE.Vector3(0, 1, 0), input.yaw);
                forward.y = 0;
                forward.normalize();
                
                const right = new THREE.Vector3(1, 0, 0);
                right.applyAxisAngle(new THREE.Vector3(0, 1, 0), input.yaw);
                right.y = 0;
                right.normalize();
                
                const moveDir = new THREE.Vector3()
                    .addScaledVector(forward, input.move.z)
                    .addScaledVector(right, input.move.x)
                    .normalize()
                    .multiplyScalar(CONFIG.MOVE_SPEED * delta);
                
                // 尝试移动 XZ
                const oldPos = player.position.clone();
                player.position.x += moveDir.x;
                player.position.z += moveDir.z;
                
                if (checkCollision(player.position)) {
                    player.position.x = oldPos.x;
                    player.position.z = oldPos.z;
                }
                
                // 手臂摆动
                const swing = Math.sin(now * 0.012) * 0.08;
                armMesh.position.x = 0.25 + swing;
                armMesh.rotation.z = -swing * 0.5;
            } else {
                armMesh.position.x = 0.25;
                armMesh.rotation.set(0, 0, 0);
            }
            
            // 重力
            player.velocity.y -= CONFIG.GRAVITY * delta;
            player.position.y += player.velocity.y * delta;
            
            // 地面检测
            if (checkCollision(player.position)) {
                if (player.velocity.y < 0) {
                    // 落地
                    player.velocity.y = 0;
                    player.onGround = true;
                    // 修正位置防止嵌入
                    player.position.y = Math.ceil(player.position.y - CONFIG.PLAYER_HEIGHT) + CONFIG.PLAYER_HEIGHT + 0.01;
                } else {
                    // 撞头
                    player.velocity.y = 0;
                    player.position.y -= 0.05;
                }
            } else {
                player.onGround = false;
            }
            
            // 同步相机位置
            camera.position.copy(player.position);
            
            // 更新高亮框
            raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
            const hits = raycaster.intersectObjects(blocks);
            if (hits.length > 0 && hits[0].distance < CONFIG.INTERACT_DISTANCE) {
                highlightMesh.position.copy(hits[0].object.position);
                highlightMesh.visible = true;
            } else {
                highlightMesh.visible = false;
            }
            
            renderer.render(scene, camera);
        }

        // ========== 初始化 ==========
        function init() {
            generateTerrain();
            setupMobileControls();
            setupDesktopControls();
            
            // 玩家初始位置
            player.position.set(0, 20, 0);
            camera.position.copy(player.position);
            
            // 隐藏加载提示
            setTimeout(() => {
                document.getElementById('loading').classList.add('hidden');
            }, 800);
            
            // 窗口调整
            window.addEventListener('resize', () => {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            });
            
            animate();
        }

        // 启动
        init();
    </script>
</body>
</html>
        
编辑器加载中
预览
控制台