🎮ArcadeLab

优化版 3D 红衣军 - Three.js

by TurboKoala83
293 lines12.2 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>优化版 3D 红衣军 - Three.js</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #87CEEB; }
        canvas { display: block; }
        /* 模拟游戏UI */
        .ui { position: absolute; top: 20px; left: 20px; color: white; font-family: sans-serif; pointer-events: none; }
        .crosshair { position: absolute; top: 50%; left: 50%; width: 20px; height: 20px; border: 2px solid rgba(255,255,255,0.5); border-radius: 50%; transform: translate(-50%, -50%); pointer-events: none; }
    </style>
</head>
<body>
    <div class="ui">优化版 3D 建模演示</div>
    <div class="crosshair"></div>

    <!-- 使用较新的 Three.js 版本以支持 CapsuleGeometry (胶囊体) -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r160/three.min.js"></script>

    <script>
        // 1. 初始化场景、相机、渲染器
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x9ac8e8); // 更自然的天蓝色
        scene.fog = new THREE.Fog(0x9ac8e8, 15, 60); // 远处雾化,增加深度感

        const camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 1.6, 3.5); 
        camera.lookAt(0, 1.2, 0);

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 柔和阴影
        renderer.outputColorSpace = THREE.SRGBColorSpace; // 更好的色彩表现
        document.body.appendChild(renderer.domElement);

        // 2. 优化的光照系统
        const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6); // 天空与地面环境光
        scene.add(hemiLight);

        const dirLight = new THREE.DirectionalLight(0xfff5e6, 1.2); // 偏暖的阳光
        dirLight.position.set(10, 20, 10);
        dirLight.castShadow = true;
        dirLight.shadow.mapSize.width = 2048;
        dirLight.shadow.mapSize.height = 2048;
        dirLight.shadow.camera.near = 1;
        dirLight.shadow.camera.far = 50;
        dirLight.shadow.camera.left = -10;
        dirLight.shadow.camera.right = 10;
        dirLight.shadow.camera.top = 10;
        dirLight.shadow.camera.bottom = -10;
        scene.add(dirLight);

        // 3. 优化地形 (有起伏的山丘)
        const terrainGeo = new THREE.PlaneGeometry(80, 80, 64, 64);
        const pos = terrainGeo.attributes.position;
        for (let i = 0; i < pos.count; i++) {
            const x = pos.getX(i);
            const y = pos.getY(i);
            // 使用正弦波模拟地形起伏
            const z = Math.sin(x * 0.1) * Math.cos(y * 0.1) * 1.5 + Math.sin(x * 0.5) * 0.2;
            pos.setZ(i, z);
        }
        terrainGeo.computeVertexNormals();
        const terrainMat = new THREE.MeshStandardMaterial({ color: 0x4a7035, roughness: 0.9 });
        const terrain = new THREE.Mesh(terrainGeo, terrainMat);
        terrain.rotation.x = -Math.PI / 2;
        terrain.receiveShadow = true;
        scene.add(terrain);

        // 4. 优化草地 (使用 InstancedMesh 渲染数千根草)
        const grassGeo = new THREE.PlaneGeometry(0.1, 0.4);
        // 双面渲染,避免旋转时看不见
        const grassMat = new THREE.MeshStandardMaterial({ color: 0x5c8f43, side: THREE.DoubleSide, roughness: 0.8 });
        const grassCount = 3000;
        const grassMesh = new THREE.InstancedMesh(grassGeo, grassMat, grassCount);
        grassMesh.receiveShadow = true;
        grassMesh.castShadow = true;
        
        const dummy = new THREE.Object3D();
        for (let i = 0; i < grassCount; i++) {
            const x = (Math.random() - 0.5) * 30;
            const z = (Math.random() - 0.5) * 30;
            // 计算地形高度
            const y = Math.sin(x * 0.1) * Math.cos(z * 0.1) * 1.5 + Math.sin(x * 0.5) * 0.2;
            
            dummy.position.set(x, y + 0.2, z);
            dummy.rotation.y = Math.random() * Math.PI;
            dummy.rotation.x = (Math.random() - 0.5) * 0.2;
            dummy.updateMatrix();
            grassMesh.setMatrixAt(i, dummy.matrix);
        }
        scene.add(grassMesh);

        // 5. 优化红衣军建模
        const soldier = new THREE.Group();
        // 调整士兵站立在起伏地形上的高度
        soldier.position.set(0, 0.3, 0); 

        // 材质定义 (使用更细腻的质感)
        const matRedCoat = new THREE.MeshStandardMaterial({ color: 0xb30000, roughness: 0.7 }); // 红衣
        const matWhitePants = new THREE.MeshStandardMaterial({ color: 0xe6e6e6, roughness: 0.8 }); // 白裤
        const matBlackBoots = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.9 }); // 黑靴
        const matSkin = new THREE.MeshStandardMaterial({ color: 0xf1c27d, roughness: 0.5 }); // 皮肤
        const matBlackHat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.8 }); // 帽子
        const matGold = new THREE.MeshStandardMaterial({ color: 0xd4af37, metalness: 0.8, roughness: 0.3 }); // 金色装饰
        const matWood = new THREE.MeshStandardMaterial({ color: 0x4a2e15, roughness: 0.9 }); // 枪托木头
        const matMetal = new THREE.MeshStandardMaterial({ color: 0x888888, metalness: 0.9, roughness: 0.2 }); // 枪管金属

        // 躯干 (使用胶囊体,更贴近人体曲线)
        const torso = new THREE.Mesh(new THREE.CapsuleGeometry(0.25, 0.5, 4, 16), matRedCoat);
        torso.position.y = 1.15;
        torso.castShadow = true;
        soldier.add(torso);

        // 军装下摆 (用圆柱体模拟长外套的下半部分)
        const coatSkirt = new THREE.Mesh(new THREE.CylinderGeometry(0.28, 0.32, 0.4, 16), matRedCoat);
        coatSkirt.position.y = 0.75;
        coatSkirt.castShadow = true;
        soldier.add(coatSkirt);

        // 白色交叉背带 (使用两个细长的扁长方体)
        const beltGeo = new THREE.BoxGeometry(0.06, 0.8, 0.02);
        const belt1 = new THREE.Mesh(beltGeo, matWhitePants);
        belt1.position.set(0, 1.15, 0.26);
        belt1.rotation.z = Math.PI / 4;
        soldier.add(belt1);
        
        const belt2 = new THREE.Mesh(beltGeo, matWhitePants);
        belt2.position.set(0, 1.15, 0.26);
        belt2.rotation.z = -Math.PI / 4;
        soldier.add(belt2);

        // 头部和五官
        const head = new THREE.Mesh(new THREE.SphereGeometry(0.2, 32, 32), matSkin);
        head.position.y = 1.65;
        head.castShadow = true;
        soldier.add(head);

        // 鼻子 (小圆锥体)
        const nose = new THREE.Mesh(new THREE.ConeGeometry(0.04, 0.1, 8), matSkin);
        nose.position.set(0, 1.63, 0.2);
        nose.rotation.x = Math.PI / 2;
        soldier.add(nose);

        // 18世纪三角帽 (用三个倾斜的平面组合)
        const hatGroup = new THREE.Group();
        const brimGeo = new THREE.CylinderGeometry(0.32, 0.32, 0.04, 3);
        const brim = new THREE.Mesh(brimGeo, matBlackHat);
        brim.rotation.y = Math.PI / 6;
        hatGroup.add(brim);
        
        const crown = new THREE.Mesh(new THREE.SphereGeometry(0.2, 16, 8, 0, Math.PI * 2, 0, Math.PI / 2), matBlackHat);
        crown.position.y = 0.06;
        hatGroup.add(crown);
        
        // 帽徽
        const badge = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.08, 0.02), matGold);
        badge.position.set(0, 0.08, 0.28);
        hatGroup.add(badge);

        hatGroup.position.y = 1.82;
        hatGroup.castShadow = true;
        soldier.add(hatGroup);

        // 四肢 (使用胶囊体)
        const armGeo = new THREE.CapsuleGeometry(0.08, 0.4, 4, 12);
        const legGeo = new THREE.CapsuleGeometry(0.1, 0.45, 4, 12);

        // 左臂 (持枪姿势)
        const leftArm = new THREE.Mesh(armGeo, matRedCoat);
        leftArm.position.set(-0.35, 1.2, 0.15);
        leftArm.rotation.set(-1.2, 0, 0.2); // 抬高并向前伸
        leftArm.castShadow = true;
        soldier.add(leftArm);

        // 右臂 (托枪姿势)
        const rightArm = new THREE.Mesh(armGeo, matRedCoat);
        rightArm.position.set(0.35, 1.1, -0.1);
        rightArm.rotation.set(-1.5, 0, -0.1);
        rightArm.castShadow = true;
        soldier.add(rightArm);

        // 左腿
        const leftLeg = new THREE.Mesh(legGeo, matWhitePants);
        leftLeg.position.set(-0.15, 0.55, 0);
        leftLeg.castShadow = true;
        soldier.add(leftLeg);

        // 右腿
        const rightLeg = new THREE.Mesh(legGeo, matWhitePants);
        rightLeg.position.set(0.15, 0.55, 0);
        rightLeg.castShadow = true;
        soldier.add(rightLeg);

        // 靴子
        const bootGeo = new THREE.BoxGeometry(0.18, 0.2, 0.35);
        const leftBoot = new THREE.Mesh(bootGeo, matBlackBoots);
        leftBoot.position.set(-0.15, 0.1, 0.05);
        leftBoot.castShadow = true;
        soldier.add(leftBoot);

        const rightBoot = new THREE.Mesh(bootGeo, matBlackBoots);
        rightBoot.position.set(0.15, 0.1, 0.05);
        rightBoot.castShadow = true;
        soldier.add(rightBoot);

        // 枪械细节优化 (枪管 + 枪托 + 扳机)
        const gunGroup = new THREE.Group();
        
        const barrel = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 1.1, 12), matMetal);
        barrel.rotation.x = Math.PI / 2;
        barrel.position.z = 0.4;
        gunGroup.add(barrel);

        const stock = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.12, 0.6), matWood);
        stock.position.z = -0.25;
        gunGroup.add(stock);
        
        const triggerGuard = new THREE.Mesh(new THREE.TorusGeometry(0.04, 0.01, 8, 16), matMetal);
        triggerGuard.rotation.x = Math.PI / 2;
        triggerGuard.position.set(0, -0.06, -0.1);
        gunGroup.add(triggerGuard);

        // 将枪调整到与左臂对齐
        gunGroup.position.set(-0.1, 1.25, 0.3);
        gunGroup.rotation.set(-0.1, 0.1, 0);
        gunGroup.castShadow = true;
        soldier.add(gunGroup);

        scene.add(soldier);

        // 6. 第一人称视角武器与手部优化
        const fpGroup = new THREE.Group();
        
        // 玩家第一人称手部 (红衣袖子)
        const fpArm = new THREE.Mesh(new THREE.CapsuleGeometry(0.06, 0.3, 4, 12), matRedCoat);
        fpArm.position.set(0.3, -0.3, -0.5);
        fpArm.rotation.set(-1.5, 0, 0.5);
        fpGroup.add(fpArm);
        
        const fpHand = new THREE.Mesh(new THREE.SphereGeometry(0.06, 16, 16), matSkin);
        fpHand.position.set(0.3, -0.15, -0.8);
        fpGroup.add(fpHand);

        // 玩家第一人称枪械
        const fpBarrel = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 1.2, 12), matMetal);
        fpBarrel.rotation.x = Math.PI / 2;
        fpBarrel.position.set(0.25, -0.15, -1.0);
        fpGroup.add(fpBarrel);

        const fpStock = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.1, 0.5), matWood);
        fpStock.position.set(0.25, -0.18, -0.4);
        fpGroup.add(fpStock);

        // 将第一人称武器绑定到相机
        camera.add(fpGroup);
        scene.add(camera);

        // 7. 动画循环 (增加呼吸和草叶微动)
        let clock = new THREE.Clock();

        function animate() {
            requestAnimationFrame(animate);
            const time = clock.getElapsedTime();
            const delta = clock.getDelta();

            // 士兵呼吸动画 (微微起伏)
            soldier.position.y = 0.3 + Math.sin(time * 2) * 0.01;
            torso.scale.set(1 + Math.sin(time * 2) * 0.01, 1, 1 + Math.sin(time * 2) * 0.01);
            
            // 枪口微动
            gunGroup.rotation.x = -0.1 + Math.sin(time * 3) * 0.01;

            // 草地风吹动效 (通过整体轻微旋转很难,这里调整整体草的渲染不现实,可略过,但可以做相机轻微晃动)
            camera.position.y = 1.6 + Math.sin(time * 1.5) * 0.005;

            renderer.render(scene, camera);
        }

        // 8. 响应窗口大小变化
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        animate();
    </script>
</body>
</html>

Game Source: 优化版 3D 红衣军 - Three.js

Creator: TurboKoala83

Libraries: three

Complexity: complex (293 lines, 12.2 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: 3d-three-js-turbokoala83-mujg7pgh" to link back to the original. Then publish at arcadelab.ai/publish.