threejs用octree实现房间内第三人称漫游并且添加了碰撞,人物在碰撞到墙壁的时候不停的弹回,应该怎么修改才能不会疯狂的弹回?

threejs用octree实现房间内第三人称漫游并且添加了碰撞,人物在碰撞到墙壁的时候不停的弹回,应该怎么修改才能不会疯狂的弹回

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Three.js Scene</title>
    <style>
        body { margin: 0; }
        canvas { width: 100%; height: 100% }
    </style>
</head>
<body>
    <script type="importmap">
        {
            "imports": {
                "three": "./three172/build/three.module.js",
                "three/addons/": "./three172/examples/jsm/"
            }
        }
    </script>

    <script type="module">
        import * as THREE from 'three';
        import {OrbitControls} from 'three/addons/controls/OrbitControls.js'
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
        import { RGBELoader  } from 'three/addons/loaders/RGBELoader.js'
        import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
        let scene,camera,renderer,controls,npc,npcMixer
        const keyStates = {
            W: false,
            A: false,
            S: false,
            D: false,
        };

        const initScene = () => {
           
            // 创建场景
            scene = new THREE.Scene();
            

            // 创建相机
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
            // camera.position.z = 5;
            window.camera = camera

            // 创建渲染器
            renderer = new THREE.WebGLRenderer();
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            
            // 鼠标控制相机
            // controls = new OrbitControls(camera, renderer.domElement);

            // 创建蓝色网格
            const gridHelper = new THREE.GridHelper(100, 100, 0x0000ff, 0x0000ff);
            scene.add(gridHelper);

            const light = new THREE.AmbientLight(0xffffff);
            scene.add(light);
            
        }

        let v = new THREE.Vector3(0, 0, 0);//初始速度设置为0
        const a = 12;//加速度:调节按键加速快慢
        const damping = -0.04;//阻尼 当没有WASD加速的时候,人、车等玩家角色慢慢减速停下来
        const vMax = 5;//限制玩家角色最大速度
        
        let deltaPos
        let front
        const clock = new THREE.Clock();
        function animate() {

            let deltaTime = clock.getDelta();
            if(v.length()<vMax){
                if(keyStates.W){
                    console.log('w');
                    
                    // front = new THREE.Vector3(0, 0, 1);
                    
                    const front = new THREE.Vector3();
                    //获取玩家角色(相机)正前方
                    npc.getWorldDirection(front);
                    v.add(front.multiplyScalar(a * deltaTime));

                }
                if(keyStates.S){
                    console.log('S');
                    // front = new THREE.Vector3(0, 0, -1);
                    const front = new THREE.Vector3();
                    //获取玩家角色(相机)正前方
                    npc.getWorldDirection(front);
                    v.add(front.multiplyScalar(-a * deltaTime));
                }
                if (keyStates.A) {//向左运动
                    const front = new THREE.Vector3();
                    npc.getWorldDirection(front);
                    const up = new THREE.Vector3(0, 1, 0);//y方向

                    const left = up.clone().cross(front);
                    v.add(left.multiplyScalar(a * deltaTime));
                }
                if (keyStates.D) {//向右运动
                    const front = new THREE.Vector3();
                    npc.getWorldDirection(front);
                    const up = new THREE.Vector3(0, 1, 0);//y方向
                    //叉乘获得垂直于向量up和front的向量 左右与叉乘顺序有关,可以用右手螺旋定则判断,也可以代码测试结合3D场景观察验证
                    const right = front.clone().cross(up);
                    v.add(right.multiplyScalar(a * deltaTime));
                }

            }

            v.addScaledVector(v, damping);//阻尼减速
            
            deltaPos = v.clone().multiplyScalar(deltaTime);
            npc&&npc.position.add(deltaPos);//更新玩家角色的位置
            
            renderer.render(scene, camera);
            npcMixer&&npcMixer.update(deltaTime);//这里给动画设置更新速度,因为默认是一秒钟渲染60次,所以这里设置为1/60的值,
            requestAnimationFrame(animate);
            scene.backgroundRotation.y +=0.0001
        }

        const loadHRd = () => {
            //地址https://polyhaven.com/a/kloofendal_48d_partly_cloudy_puresky
            const rgbeLoader = new RGBELoader();
            rgbeLoader.load('./assets/day.hdr', (texture) => {
                // 设置纹理的格式和包围盒
                texture.mapping = THREE.EquirectangularReflectionMapping;
                scene.background = texture; // 设置为场景背景
                scene.environment = texture; // 设置为环境光照
            });
        }
        
        function addModels(path){
            const loader = new GLTFLoader()
            const dracoLoader = new DRACOLoader()
            dracoLoader.setDecoderPath('./three172/examples/jsm/libs/draco/')
            loader.setDRACOLoader(dracoLoader)
            loader.load(path, (gltf) => {
                gltf.scene.position.set(0, 0, 0);
                
                gltf.scene.children[0].position.set(0, 0, 0);
                scene.add(gltf.scene)
            })
        }
        
        const listener = () => {
            let leftButtonBool = false;//记录鼠标左键状态
            document.addEventListener('mousedown', () => {
                leftButtonBool = true;
            });
            document.addEventListener('mouseup', () => {
                leftButtonBool = false;
            });
            
            document.addEventListener('mousemove', (event) => {
                if(leftButtonBool){
                    npc.rotation.y -= event.movementX / 300;
                    camera.rotation.x -= event.movementY / 1200;
                    
                }
            });
            document.addEventListener('keydown', (event) => {
                if (event.code === 'KeyW') keyStates.W = true;
                if (event.code === 'KeyA') keyStates.A = true;
                if (event.code === 'KeyS') keyStates.S = true;
                if (event.code === 'KeyD') keyStates.D = true;
            });
            document.addEventListener('keyup', (event) => {
                if (event.code === 'KeyW') keyStates.W = false;
                if (event.code === 'KeyA') keyStates.A = false;
                if (event.code === 'KeyS') keyStates.S = false;
                if (event.code === 'KeyD') keyStates.D = false;
            });
        }
        const addNpc = () => {
            const loader = new GLTFLoader();
            
            const dracoLoader = new DRACOLoader()
            dracoLoader.setDecoderPath('./three172/examples/jsm/libs/draco/')
            loader.setDRACOLoader(dracoLoader)
            // 加载NPC模型
            loader.load('./assets/Jackie.glb', (obj) => {

                npc = obj.scene;
                npc.name = 'npc';
                npc.position.set(0, 0, 0);
                // 将NPC模型添加到场景
                scene.add(npc);

                npcMixer = new THREE.AnimationMixer(npc);
                let runAction = npcMixer.clipAction(obj.animations[0]);
                runAction.setLoop(THREE.LoopRepeat)
                runAction.play()

                npc.add(camera)
                
                
                camera.position.y = 2;
                camera.position.z = -3;
                camera.rotateY(Math.PI/1);
            });
        }
            
        listener()
        initScene()
        animate();
        loadHRd()
        addModels('./assets/jifang.glb')
        addNpc()
    </script>
</body>
</html>

视频地址
https://tc.qdqqd.com/OUW9Hw.mp4

尝试过改变碰撞后位移的值
capsule&&capsule.translate(result.normal.multiplyScalar( result.depth));

阅读 781
1 个回答
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Three.js Scene</title>
    <style>
        body { margin: 0; }
        canvas { width: 100%; height: 100% }
    </style>
</head>
<body>
    <script type="importmap">
        {
            "imports": {
                "three": "./three172/build/three.module.js",
                "three/addons/": "./three172/examples/jsm/"
            }
        }
    </script>

    <script type="module">
        import * as THREE from 'three';
        import {OrbitControls} from 'three/addons/controls/OrbitControls.js'
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
        import { RGBELoader  } from 'three/addons/loaders/RGBELoader.js'
        import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
        import { Octree } from 'three/addons/math/Octree.js';
        import { Capsule } from 'three/addons/math/Capsule.js';
        
        let scene, camera, renderer, controls, npc, npcMixer;
        let worldOctree = new Octree();
        let playerCapsule;
        
        const keyStates = {
            W: false,
            A: false,
            S: false,
            D: false,
        };

        const initScene = () => {
           
            // 创建场景
            scene = new THREE.Scene();
            
            // 创建相机
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
            window.camera = camera

            // 创建渲染器
            renderer = new THREE.WebGLRenderer();
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);
            
            // 创建蓝色网格
            const gridHelper = new THREE.GridHelper(100, 100, 0x0000ff, 0x0000ff);
            scene.add(gridHelper);

            const light = new THREE.AmbientLight(0xffffff);
            scene.add(light);
            
        }

        let v = new THREE.Vector3(0, 0, 0);//初始速度设置为0
        const a = 12;//加速度:调节按键加速快慢
        const damping = -0.04;//阻尼 当没有WASD加速的时候,人、车等玩家角色慢慢减速停下来
        const vMax = 5;//限制玩家角色最大速度
        
        let deltaPos;
        let front;
        const clock = new THREE.Clock();
        
        function playerCollisions() {
            if (!playerCapsule || !npc) return;
            
            // 更新胶囊体位置
            playerCapsule.start.set(
                npc.position.x,
                npc.position.y + 0.35,
                npc.position.z
            );
            playerCapsule.end.set(
                npc.position.x,
                npc.position.y + 1.6,
                npc.position.z
            );
            
            // 检测碰撞
            const result = worldOctree.capsuleIntersect(playerCapsule);
            
            if (result) {
                // 碰撞处理:调整位置避免穿透
                npc.position.add(result.normal.multiplyScalar(result.depth));
                
                // 调整速度向量:将速度向量投影到碰撞平面上
                // 这样速度会沿着墙壁方向滑动,而不是弹回
                const dot = v.dot(result.normal);
                if (dot < 0) {
                    v.sub(result.normal.multiplyScalar(dot));
                    // 添加一些摩擦力,减慢与墙壁平行的移动
                    v.multiplyScalar(0.8);
                }
            }
        }
        
        function animate() {
            let deltaTime = clock.getDelta();
            
            if(v.length() < vMax) {
                if(keyStates.W) {
                    const front = new THREE.Vector3();
                    npc.getWorldDirection(front);
                    v.add(front.multiplyScalar(a * deltaTime));
                }
                if(keyStates.S) {
                    const front = new THREE.Vector3();
                    npc.getWorldDirection(front);
                    v.add(front.multiplyScalar(-a * deltaTime));
                }
                if (keyStates.A) {
                    const front = new THREE.Vector3();
                    npc.getWorldDirection(front);
                    const up = new THREE.Vector3(0, 1, 0);
                    const left = up.clone().cross(front);
                    v.add(left.multiplyScalar(a * deltaTime));
                }
                if (keyStates.D) {
                    const front = new THREE.Vector3();
                    npc.getWorldDirection(front);
                    const up = new THREE.Vector3(0, 1, 0);
                    const right = front.clone().cross(up);
                    v.add(right.multiplyScalar(a * deltaTime));
                }
            }

            v.addScaledVector(v, damping);//阻尼减速
            
            if (npc) {
                // 保存上一次位置,如果发生碰撞可以回退
                const previousPosition = npc.position.clone();
                
                // 应用速度
                deltaPos = v.clone().multiplyScalar(deltaTime);
                npc.position.add(deltaPos);
                
                // 碰撞检测和处理
                playerCollisions();
            }
            
            renderer.render(scene, camera);
            npcMixer && npcMixer.update(deltaTime);
            requestAnimationFrame(animate);
            scene.backgroundRotation && (scene.backgroundRotation.y += 0.0001);
        }

        const loadHRd = () => {
            const rgbeLoader = new RGBELoader();
            rgbeLoader.load('./assets/day.hdr', (texture) => {
                texture.mapping = THREE.EquirectangularReflectionMapping;
                scene.background = texture;
                scene.environment = texture;
            });
        }
        
        function addModels(path) {
            const loader = new GLTFLoader()
            const dracoLoader = new DRACOLoader()
            dracoLoader.setDecoderPath('./three172/examples/jsm/libs/draco/')
            loader.setDRACOLoader(dracoLoader)
            loader.load(path, (gltf) => {
                gltf.scene.position.set(0, 0, 0);
                gltf.scene.children[0].position.set(0, 0, 0);
                scene.add(gltf.scene);
                
                // 将模型添加到Octree中
                worldOctree.fromGraphNode(gltf.scene);
                console.log("模型已添加到Octree");
            })
        }
        
        const listener = () => {
            let leftButtonBool = false;
            document.addEventListener('mousedown', () => {
                leftButtonBool = true;
            });
            document.addEventListener('mouseup', () => {
                leftButtonBool = false;
            });
            
            document.addEventListener('mousemove', (event) => {
                if(leftButtonBool){
                    npc.rotation.y -= event.movementX / 300;
                    camera.rotation.x -= event.movementY / 1200;
                }
            });
            document.addEventListener('keydown', (event) => {
                if (event.code === 'KeyW') keyStates.W = true;
                if (event.code === 'KeyA') keyStates.A = true;
                if (event.code === 'KeyS') keyStates.S = true;
                if (event.code === 'KeyD') keyStates.D = true;
            });
            document.addEventListener('keyup', (event) => {
                if (event.code === 'KeyW') keyStates.W = false;
                if (event.code === 'KeyA') keyStates.A = false;
                if (event.code === 'KeyS') keyStates.S = false;
                if (event.code === 'KeyD') keyStates.D = false;
            });
        }
        
        const addNpc = () => {
            const loader = new GLTFLoader();
            
            const dracoLoader = new DRACOLoader()
            dracoLoader.setDecoderPath('./three172/examples/jsm/libs/draco/')
            loader.setDRACOLoader(dracoLoader)
            
            loader.load('./assets/Jackie.glb', (obj) => {
                npc = obj.scene;
                npc.name = 'npc';
                npc.position.set(0, 0, 0);
                scene.add(npc);

                npcMixer = new THREE.AnimationMixer(npc);
                let runAction = npcMixer.clipAction(obj.animations[0]);
                runAction.setLoop(THREE.LoopRepeat)
                runAction.play()

                npc.add(camera)
                camera.position.y = 2;
                camera.position.z = -3;
                camera.rotateY(Math.PI/1);
                
                // 创建玩家胶囊体用于碰撞检测
                playerCapsule = new Capsule(
                    new THREE.Vector3(0, 0.35, 0),
                    new THREE.Vector3(0, 1.6, 0),
                    0.35
                );
            });
        }
            
        listener()
        initScene()
        addModels('./assets/jifang.glb')
        addNpc()
        loadHRd()
        animate();
    </script>
</body>
</html>
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题