利用three实现胶囊的移动,以及相机的控制跟踪

121 阅读3分钟

创建物理世界类

class Physics{
     // 创建碰撞世界
    constructor(
        planeGroup, // 需要被检测碰撞的墙体
        scene, // 场景
        camera
    ){
        // 创建八叉树世界
        this.octreeWrold = new Octree()
        // 传入被墙体,创建八叉树节点
        this.octreeWrold.fromGraphNode(planeGroup)
        console.log('planeGroup',planeGroup)
        // 创建一个人的碰撞体(胶囊)
        this.playerCollider = new Capsule(
            new THREE.Vector3(0,0.35,0),// 圆柱下圆心位置
            new THREE.Vector3(0,1.35,0), // 上圆圆心位置
            0.35 // 上下半圆的半径
        )
        this.playerCollider.start.set(0, 2.35, 0);
        this.playerCollider.end.set(0, 3.35, 0);
        // 加载模型
        const gltfLoader = new GLTFLoader()
        // 创建一个空的3dObject
        this.capsule = new THREE.Object3D()
        this.capsule.position.set(0,0,0)
        // 声明动作混合器
        this.mixer = null
        this.actions = []
        // 当前激活的动作
        this.activeAction = null
        gltfLoader.load('path', glb => {
            this.robot = glb.scene
            // 所以许欸
            this.robot.scale.set(0.5,0.5,0.5)
            this.robot.position.set(0,-0.88,0)
            // 将模型add到capsule上面
            this.capsule.add(this.robot)
            // 创建动画混合器,创建存储动画对象
            this.mixer = new THREE.AnimationMixer(this.robot)
            glb.animations.forEach((animation) => {
                let name = animation.name
                // 创建动画片段,存到动画对象内
                this.actions[name] = this.mixer.clipAction(animation)
                // 处理动画片段
                if(name == 'Idle' || name == 'Walking' || name == 'Running'){
                    // 这些动画需要循环并且不需要停止在动画的最后一帧
                    animation.clampWhenFinished = false
                    animation.loop = THREE.LoopRepeat
                }else{
                    // 其他动画
                    animation.clampWhenFinished = true
                    animation.loop = THREE.LoopOnce
                }
            })
            // 设置默认动画,发呆
            this.activeAction = this.actions['Idle']
            this.activeAction.play()
            // 加入到场景内
            scene.add(this.capsule)
            
        })
        // 声明另一个相机
        const backCamera = new THREE.PerspectiveCamera(
            70,
            window.innerWidth / window.innerHeight,
            0.001,
            1000
        )
        // 创建一个控制相机上下移动的对象
        this.cameraControl = new THREE.Object3D()
        // 设置两个相机的位置
        camera.position.set(0,2,-5)
        camera.lookAt(this.capsule.position)
        backCamera.position.set(0,2,5)
        backCamera.lookAt(this.capsule.position)
        this.cameraControl.add(camera)
        this.cameraControl.add(backCamera)
        // 将相机控制对象添加到模型胶囊内
        this.capsule.add(this.cameraControl)
        // 通过旋转相机控制对象来操作视角
        document.addEventListener('mousemove',(e)=>{
            // 左右移动鼠标操作模型移动,横向移动鼠标就是模型沿着Y轴旋转
            this.capsule.rotation.y -= e.movementX * 0.003
            // 垂直移动鼠标就是相机控制对象沿着X轴旋转
            this.cameraControl.rotation.x += e.movementY * 0.003
            if(this.cameraControl.rotation.x >  Math.PI / 8){
                // 限制旋转角度
                this.cameraControl.rotation.x = Math.PI / 8
            }else if(
                this.cameraControl.rotation.x < - Math.PI / 8
            ){
                this.cameraControl.rotation.x = -Math.PI / 8
            }
        })
        // 锁定鼠标
        document.addEventListener('mousedown', e => {
            document.body.requestPointerLock()
        } )
        // 设置胶囊物体场景
        this.gravity = -9.8
        // 玩家的速度向量
        this.playerVelocity = new THREE.Vector3(0,0,0)
        // 玩家的方向向量
        this.playerDerection = new THREE.Vector3(0,0,0)
        // 检测玩家是否在地上
        this.playerOnFloor = false
        
        // 键盘状态
        this.keyStates = {
            KeyW : false,
            KeyA : false,
            KeyS : false,
            KeyD : false,
            Space : false,
            isDown : false, // 键盘被按下
        }
        // 键盘抬起
        document.addEventListener('keydown', e=> {
            this.keyStates[e.code] = true
            this.keyStates.isDown = true
        })
        // 键盘抬起
        document.addEventListener('keyup',e => {
            this.keyStates[e.code] = false
            this.keyStates.isDown = false
        })
        
    }


}

更新用户位置的方法

    upDataPlayer(deltaTime){
        // 阻尼
        let damping = -0.25
        // 如果在地面上
        if(this.playerOnFloor){
            // 在y轴上的速度为0
            this.playerVelocity.y = 0
            // 没有按着键盘就减速
            this.keyStates.isDown ||
            // addScaledVector 两个参数相乘后与方法对象相加
            this.playerVelocity.addScaledVector(this.playerVelocity,damping) 
        }else{
            // 如果不在地面上 速度乘时间等于距离
            this.playerVelocity.y += this.gravity * deltaTime
        }
        // 计算水平面距离,速度乘时间等于距离
        const playerMoveDistance = this.playerVelocity.clone().
        multiplyScalar(deltaTime)
        // 移动用户的碰撞体胶囊距离
        this.playerCollider.translate(playerMoveDistance)
        // 移动Three.Object对象,利用capsule.getCenter方法赋予obj中心点坐标
        this.playerCollider.getCenter(this.capsule.position)

        // 检测物理碰撞
        this.playerCollisions()
        // 判断模型动作
        // 如果有水平的运动,则设置运动的动作
        if (
          Math.abs(this.playerVelocity.x) + Math.abs(this.playerVelocity.z) > 0.1 &&
          Math.abs(this.playerVelocity.x) + Math.abs(this.playerVelocity.z) <= 3
        ) {
          this.fadeToAction("Walking");
        } else if (
          Math.abs(this.playerVelocity.x) + Math.abs(this.playerVelocity.z) >
          3
        ) {
          this.fadeToAction("Running");
        } else {
          this.fadeToAction("Idle");
        }
    }

检测用户物理碰撞

    playerCollisions(){
        // 将人物碰撞胶囊传入八叉树世界,进行检测
        const result = this.octreeWrold.capsuleIntersect(this.playerCollider)
        this.playerOnFloor = false
        if(result){
            // 如果没有碰撞就返回false
            // 如果有碰撞,就返回碰撞深度与碰撞向量
            // 如果y向量不为0,则是碰到地面了
            this.playerOnFloor = result.normal.y > 0
            // 移动距离
            if(result.depth > 0.1){
                this.playerCollider.translate(result.normal.multiplyScalar(result.depth))
            }
        }

    }

监听键盘状态,更新用户速度向量的方法

    updataPlayerVelocity(deltaTime){
        if(this.keyStates['KeyW']){
            // 正方向
            this.playerDerection.z = 1
            // 获取胶囊的正前方向
            const playerFront = new THREE.Vector3(0,0,0)
            // 胶囊的正前方向赋予给playerFront
            this.capsule.getWorldDirection(playerFront)
            // 计算出玩家的速度,设置最大速度
            if (
                this.playerVelocity.x * this.playerVelocity.x +
                  this.playerVelocity.z * this.playerVelocity.z <=
                200
              ){
                // 加速度,往正前方加速
                this.playerVelocity.add(playerFront.multiplyScalar(deltaTime * 5))
              }
        }
        if(this.keyStates['KeyS']){
            // 获取胶囊的正前方向
            const playerFront = new THREE.Vector3(0,0,0)
            this.capsule.getWorldDirection(playerFront)
            // 设置速度
            this.playerVelocity.add(playerFront.multiplyScalar(-deltaTime))
        }
        if(this.keyStates['KeyA']){
            const playerFront = new THREE.Vector3(0,0,0)
            this.capsule.getWorldDirection(playerFront)
            // 侧方的方向,正前面的方向和胶囊的正上方求叉积,求出侧方的方向
            playerFront.cross(this.capsule.up);
            // 左边为负方向
            this.playerVelocity.add(playerFront.multiplyScalar(-deltaTime))
        }
        if(this.keyStates['KeyD']){
            const playerFront = new THREE.Vector3(0,0,0)
            this.capsule.getWorldDirection(playerFront)
            playerFront.cross(this.capsule.up)

            this.playerVelocity.add(playerFront.multiplyScalar(deltaTime))
        }
        if(this.keyStates['Space']
        && this.playerOnFloor
        ){
           
            this.playerVelocity.y = 5
        }
    }

重置用户的位置方法

resetPlayer() {
        if (this.capsule.position.y < -20) {
          this.playerCollider.start.set(0, 2.35, 0);
          this.playerCollider.end.set(0, 3.35, 0);
          this.playerCollider.radius = 0.35;
          this.playerVelocity.set(0, 0, 0);
        }
      }

更新用户动作方法

 fadeToAction(actionName) {
    this.prevAction = this.activeAction;
    this.activeAction = this.actions[actionName];
    if (this.prevAction != this.activeAction) {
      this.prevAction.fadeOut(0.3);
      this.activeAction
        .reset()
        .setEffectiveTimeScale(1)
        .setEffectiveWeight(1)
        .fadeIn(0.3)
        .play();

      this.mixer.addEventListener("finished", (e) => {
        this.prevAction = activeAction;
        this.activeAction = this.actions["Idle"];
        this.prevAction.fadeOut(0.3);
        this.activeAction
          .reset()
          .setEffectiveTimeScale(1)
          .setEffectiveWeight(1)
          .fadeIn(0.3)
          .play();
      });
    }
  }