83 Three.js 使用PointerLockControls控制相机实现射击游戏视角

2018-04-02 22:21:34 人阅读

案例查看有问题?
全屏试试!

案例解析

 

简历

指针锁定API允许您在游戏界面中锁定鼠标或其他指针设备,以便您不用绝对定位光标就可以获得坐标变化值,从而准确地判断用户正在做什么,并且还可以防止用户意外地进入另一块屏幕或别的什么地方,从而导致误操作。 
这一篇是我从官网上获得的相关控制器的,然后通过官网的案例进行一下修改扩展了一下功能。将案例实现了简单的碰撞检测。

案例实现

案例查看地址:

实现鼠标锁定

  • 首先,我们在body里面添加了dom标签,因为鼠标锁定只有通过用户才可以触发。
<div id="blocker">      <div id="instructions">          <span style="font-size:40px">点击屏幕开始</span>          <br />          <br />          (W, A, S, D = 移动, SPACE = 跳跃, MOUSE = 移动视角)      </div>  </div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 然后,判断一下当前浏览器是否支持鼠标锁定。并绑定到了鼠标点击事件:
instructions.addEventListener( 'click', function ( event ) {      instructions.style.display = 'none';     // 锁定鼠标光标      element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;      element.requestPointerLock();  }, false );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

实现鼠标改变视角

  • 这里,就需要我们的PointerLockControls控件进行控制了,先引入PointerLockControls控件文件:
<script src="/lib/js/controls/PointerLockControls.js"></script>
  • 1
  • 实例化控件,并把相机传入:
controls = new THREE.PointerLockControls( camera );
  • 1
  • 通过实例化的对象可以通过getObject()获取到控制对象,可以设置它的位置来调整进入场景的位置。最后将对象放置到scene场景当中。
controls.getObject().position.y = 50;  controls.getObject().position.x = 100;  scene.add( controls.getObject() );
  • 1
  • 2
  • 3
  • 最后,在render当中,我们让controls调用update函数实现更新
//获取到控制器对象  var control = controls.getObject(); //获取刷新时间  var delta = clock.getDelta();  //根据速度值移动控制器  control.translateX( velocity.x * delta );  control.translateY( velocity.y * delta ); control.translateZ( velocity.z * delta );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

实现按键移动

我们要实现可以通过键盘案例进行控制器位置调整,首先就是要监听事件,所以我们监听的键盘按下事件和键盘抬起事件:

document.addEventListener( 'keydown', onKeyDown, false );  document.addEventListener( 'keyup', onKeyUp, false );
  • 1
  • 2

然后再监听回调里面判断当前的键盘按键,符合条件的,就将当前的方向设置为true:

var onKeyDown = function ( event ) {      switch ( event.keyCode ) {          case 38: // up          case 87: // w              moveForward = true;              break;          case 37: // left          case 65: // a              moveLeft = true; break;          case 40: // down          case 83: // s              moveBackward = true;              break;          case 39: // right          case 68: // d              moveRight = true;              break;          case 32: // space              if ( canJump && spaceUp ) velocity.y += upSpeed;              canJump = false;              spaceUp = false;              break;      }  };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

最后,在render渲染中,进行判断当前的移动方向,实现的当前的移动,注意velocity向量,这是一个缓冲值,为了保证鼠标抬起后,场景不直接暂停,而是有一个简短的过渡效果:

if ( moveForward || moveBackward ) velocity.z -= direction.z * speed * delta;  if ( moveLeft || moveRight ) velocity.x -= direction.x * speed * delta;   //根据速度值移动控制器  control.translateX( velocity.x * delta );  control.translateY( velocity.y * delta );  control.translateZ( velocity.z * delta );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用Raycaster实现简单的碰撞检测

这个部分是个难点,虽然以后会有更好的方法,但是,这个确实是一个好的练手方案。之前,我们使用Raycaster来进行窗口二维位置转three.js中的坐标位置。但是,Raycaster还有一个妙用,就是检测前方一定距离内是否有物体相撞。让我讲解一下实现原理。 
- 首先,实例化Raycaster对象,然后,设置好Raycaster对象的,位置,方向,还有长度。Raycaster(起源:Vector3,方向:Vector3,近:浮动,远:浮动)

//声明射线  var upRaycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3( 0, 1, 0), 0, 10);  var horizontalRaycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(), 0, 10);  var downRaycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3( 0, -1, 0), 0, 10);
  • 1
  • 2
  • 3
  • 4

虽然声明了三个,但是,其实用到了只用了两个,第三个向上的判断也没有写。

然后,将当前控制器的位置赋值给射线

//复制相机的位置  downRaycaster.ray.origin.copy( control.position );
  • 1
  • 2

判断当前射线的线朝向的方向是否有物体

//判断是否停留在了立方体上面  var intersections = downRaycaster.intersectObjects( scene.children, true);  var onObject = intersections.length > 0;  //判断是否停在了立方体上面  if ( onObject === true ) {      velocity.y = Math.max( 0, velocity.y );      canJump = true;  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这是y轴的判断,如果脚下有物体,则不再下坠。实现了简单的碰撞检测。

 

 

案例源代码


                        
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        html, body {
            margin: 0;
            height: 100%;
        }
 
        canvas {
            display: block;
        }
        #blocker {
            position: absolute;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.5);
        }
        #instructions {
            width: 100%;
            height: 100%;
            display: -webkit-box;
            display: -moz-box;
            display: box;
            -webkit-box-orient: horizontal;
            -moz-box-orient: horizontal;
            box-orient: horizontal;
            -webkit-box-pack: center;
            -moz-box-pack: center;
            box-pack: center;
            -webkit-box-align: center;
            -moz-box-align: center;
            box-align: center;
            color: #ffffff;
            text-align: center;
            cursor: pointer;
        }
 
    </style>
</head>
 
<body onload="draw();">
<div id="blocker">
 
    <div id="instructions">
        <span style="font-size:40px">点击屏幕开始</span>
        <br />
        <br />
        (W, A, S, D = 移动, SPACE = 跳跃, MOUSE = 移动视角)
    </div>
 
</div>
</body>
<script src="/lib/three.js"></script>
<script src="/lib/js/loaders/OBJLoader.js"></script>
<script src="/lib/js/loaders/MTLLoader.js"></script>
<script src="/lib/libs/chroma.js"></script> <!--处理颜色的库-->
<script src="/lib/js/controls/PointerLockControls.js"></script>
<script src="/lib/js/libs/stats.min.js"></script>
<script src="/lib/js/libs/dat.gui.min.js"></script>
<script src="/lib/js/Detector.js"></script>
 
<script>
    var renderer,camera,scene,gui,light,stats,controls;
    var clock = new THREE.Clock();
    //是否锁定页面的相关
    var blocker = document.getElementById( 'blocker' );
    var instructions = document.getElementById( 'instructions' );
    //移动相关的变量
    var controlsEnabled = false;
    var moveForward = false;
    var moveBackward = false;
    var moveLeft = false;
    var moveRight = false;
    var canJump = false;
    var spaceUp = true; //处理一直按着空格连续跳的问题
    //声明射线
    var upRaycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3( 0, 1, 0), 0, 10);
    var horizontalRaycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(), 0, 10);
    var downRaycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3( 0, -1, 0), 0, 10);
 
    var velocity = new THREE.Vector3(); //移动速度变量
    var direction = new THREE.Vector3(); //移动的方向变量
    var rotation = new THREE.Vector3(); //当前的相机朝向
 
    var speed = 500; //控制器移动速度
    var upSpeed = 200; //控制跳起时的速度
 
    //辅助箭头
    var up,horizontal,down,group;
 
    function initRender() {
        renderer = new THREE.WebGLRenderer({antialias:true});
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.sortObjects = false;
        //告诉渲染器需要阴影效果
        document.body.appendChild(renderer.domElement);
    }
 
    function initCamera() {
        camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
        //camera.position.set(0, 0, 50);
    }
 
    function initScene() {
        scene = new THREE.Scene();
    }
 
    //初始化dat.GUI简化试验流程
    function initGui() {
        //声明一个保存需求修改的相关数据的对象
        //gui = {};
        //var datGui = new dat.GUI();
        //将设置属性添加到gui当中,gui.add(对象,属性,最小值,最大值)
    }
 
    function initLight() {
        scene.add(new THREE.AmbientLight(0x444444));
 
        light = new THREE.PointLight(0xffffff);
        light.position.set(0,50,0);
 
        //告诉平行光需要开启阴影投射
        light.castShadow = true;
 
        scene.add(light);
    }
 
    function initModel() {
 
        //辅助工具
        var helper = new THREE.AxesHelper(50);
        //scene.add(helper);
 
        var mtlLoader = new THREE.MTLLoader();
        mtlLoader.setPath('/lib/assets/models/');
        //加载mtl文件
        mtlLoader.load('city.mtl', function (material) {
            var objLoader = new THREE.OBJLoader();
            //设置当前加载的纹理
            objLoader.setMaterials(material);
            objLoader.setPath('/lib/assets/models/');
            objLoader.load('city.obj', function (object) {
                //设置颜色的取值范围
                var scale = chroma.scale(['yellow', '008ae5']);
 
                //重新设置纹理颜色
                setRandomColors(object, scale);
 
                object.scale.set(5, 5, 5);
                //将模型缩放并添加到场景当中
                scene.add(object);
            })
        });
 
        //添加辅助线
        group = new THREE.Group();
        up = new THREE.ArrowHelper(new THREE.Vector3(0, 1, 0), new THREE.Vector3(), 10, 0x00ff00);
        horizontal = new THREE.ArrowHelper(new THREE.Vector3(1, 0, 0), new THREE.Vector3(), 10, 0x00ffff);
        down = new THREE.ArrowHelper(new THREE.Vector3(0, -1, 0), new THREE.Vector3(), 10, 0xffff00);
 
        group.add(up);
        group.add(horizontal);
        group.add(down);
 
        //scene.add(group);
    }
 
    //添加纹理的方法
    function setRandomColors(object, scale) {
        //获取children数组
        var children = object.children;
 
        //如果当前模型有子元素,则遍历子元素
        if (children && children.length > 0) {
            children.forEach(function (e) {
                setRandomColors(e, scale)
            });
        }
        else {
            if (object instanceof THREE.Mesh) {
                //如果当前的模型是楼层,则设置固定的颜色,并且透明化
                if(Array.isArray(object.material)){
                    for(var i = 0; i<object.material.length; i++){
                        var material = object.material[i];
                        var color = scale(Math.random()).hex();
                        if (material.name.indexOf("building") === 0) {
                            material.color = new THREE.Color(color);
                            material.transparent = true;
                            material.opacity = 0.7;
                            material.depthWrite = false;
                        }
                    }
                }
                // 如果不是场景组,则给当前mesh添加纹理
                else{
                    //随机当前模型的颜色
                    object.material.color = new THREE.Color(scale(Math.random()).hex());
                }
            }
        }
    }
 
    //初始化性能插件
    function initStats() {
        stats = new Stats();
        document.body.appendChild(stats.dom);
    }
 
    function initControls() {
 
        controls = new THREE.PointerLockControls( camera );
        controls.getObject().position.y = 50;
        controls.getObject().position.x = 100;
        scene.add( controls.getObject() );
        var onKeyDown = function ( event ) {
            switch ( event.keyCode ) {
                case 38: // up
                case 87: // w
                    moveForward = true;
                    break;
                case 37: // left
                case 65: // a
                    moveLeft = true; break;
                case 40: // down
                case 83: // s
                    moveBackward = true;
                    break;
                case 39: // right
                case 68: // d
                    moveRight = true;
                    break;
                case 32: // space
                    if ( canJump && spaceUp ) velocity.y += upSpeed;
                    canJump = false;
                    spaceUp = false;
                    break;
            }
        };
        var onKeyUp = function ( event ) {
            switch( event.keyCode ) {
                case 38: // up
                case 87: // w
                    moveForward = false;
                    break;
                case 37: // left
                case 65: // a
                    moveLeft = false;
                    break;
                case 40: // down
                case 83: // s
                    moveBackward = false;
                    break;
                case 39: // right
                case 68: // d
                    moveRight = false;
                    break;
                case 32: // space
                    spaceUp = true;
                    break;
            }
        };
        document.addEventListener( 'keydown', onKeyDown, false );
        document.addEventListener( 'keyup', onKeyUp, false );
    }
 
    function initPointerLock() {
        //实现鼠标锁定的教程地址 http://www.html5rocks.com/en/tutorials/pointerlock/intro/
        var havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
        if ( havePointerLock ) {
            var element = document.body;
            var pointerlockchange = function ( event ) {
                if ( document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element ) {
                    controlsEnabled = true;
                    controls.enabled = true;
                    blocker.style.display = 'none';
                } else {
                    controls.enabled = false;
                    blocker.style.display = 'block';
                    instructions.style.display = '';
                }
            };
            var pointerlockerror = function ( event ) {
                instructions.style.display = '';
            };
            // 监听变动事件
            document.addEventListener( 'pointerlockchange', pointerlockchange, false );
            document.addEventListener( 'mozpointerlockchange', pointerlockchange, false );
            document.addEventListener( 'webkitpointerlockchange', pointerlockchange, false );
            document.addEventListener( 'pointerlockerror', pointerlockerror, false );
            document.addEventListener( 'mozpointerlockerror', pointerlockerror, false );
            document.addEventListener( 'webkitpointerlockerror', pointerlockerror, false );
            instructions.addEventListener( 'click', function ( event ) {
                instructions.style.display = 'none';
                //全屏
                launchFullScreen(renderer.domElement);
                // 锁定鼠标光标
                element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
                element.requestPointerLock();
            }, false );
        }
        else {
            instructions.innerHTML = '你的浏览器不支持相关操作,请更换浏览器';
        }
    }
 
    function render() {
        if ( controlsEnabled === true ) {
            //获取到控制器对象
            var control = controls.getObject();
            //获取刷新时间
            var delta = clock.getDelta();
 
            //velocity每次的速度,为了保证有过渡
            velocity.x -= velocity.x * 10.0 * delta;
            velocity.z -= velocity.z * 10.0 * delta;
            velocity.y -= 9.8 * 100.0 * delta; // 默认下降的速度
 
            //获取当前按键的方向并获取朝哪个方向移动
            direction.z = Number( moveForward ) - Number( moveBackward );
            direction.x = Number( moveLeft ) - Number( moveRight );
            //将法向量的值归一化
            direction.normalize();
 
            group.position.set(control.position.x,control.position.y,control.position.z);
 
            //判断是否接触到了模型
            rotation.copy(control.getWorldDirection().multiply(new THREE.Vector3(-1, 0, -1)));
 
            //判断鼠标按下的方向
            var m = new THREE.Matrix4();
            if(direction.z > 0){
                if(direction.x > 0){
                    m.makeRotationY(Math.PI/4);
                }
                else if(direction.x < 0){
                    m.makeRotationY(-Math.PI/4);
                }
                else{
                    m.makeRotationY(0);
                }
            }
            else if(direction.z < 0){
                if(direction.x > 0){
                    m.makeRotationY(Math.PI/4*3);
                }
                else if(direction.x < 0){
                    m.makeRotationY(-Math.PI/4*3);
                }
                else{
                    m.makeRotationY(Math.PI);
                }
            }
            else{
                if(direction.x > 0){
                    m.makeRotationY(Math.PI/2);
                }
                else if(direction.x < 0){
                    m.makeRotationY(-Math.PI/2);
                }
            }
            //给向量使用变换矩阵
            rotation.applyMatrix4(m);
            //horizontal.setDirection(rotation);
            horizontalRaycaster.set( control.position , rotation );
 
            var horizontalIntersections = horizontalRaycaster.intersectObjects( scene.children, true);
            var horOnObject = horizontalIntersections.length > 0;
 
            //判断移动方向修改速度方向
            if(!horOnObject){
                if ( moveForward || moveBackward ) velocity.z -= direction.z * speed * delta;
                if ( moveLeft || moveRight ) velocity.x -= direction.x * speed * delta;
            }
 
            //复制相机的位置
            downRaycaster.ray.origin.copy( control.position );
            //获取相机靠下10的位置
            downRaycaster.ray.origin.y -= 10;
            //判断是否停留在了立方体上面
            var intersections = downRaycaster.intersectObjects( scene.children, true);
            var onObject = intersections.length > 0;
            //判断是否停在了立方体上面
            if ( onObject === true ) {
                velocity.y = Math.max( 0, velocity.y );
                canJump = true;
            }
            //根据速度值移动控制器
            control.translateX( velocity.x * delta );
            control.translateY( velocity.y * delta );
            control.translateZ( velocity.z * delta );
 
            //保证控制器的y轴在10以上
            if ( control.position.y < 10 ) {
                velocity.y = 0;
                control.position.y = 10;
                canJump = true;
            }
        }
 
    }
 
    //窗口变动触发的函数
    function onWindowResize() {
 
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        render();
        renderer.setSize( window.innerWidth, window.innerHeight );
 
    }
 
    function animate() {
        //更新控制器
        render();
 
        //更新性能插件
        stats.update();
 
        renderer.render( scene, camera );
 
        requestAnimationFrame(animate);
    }
 
    function draw() {
        //兼容性判断
        if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
 
        initPointerLock();
        initGui();
        initRender();
        initScene();
        initCamera();
        initLight();
        initModel();
        initControls();
        initStats();
 
        animate();
        window.onresize = onWindowResize;
    }
 
    function launchFullScreen(element) {
        /*if (element.requestFullscreen) {
            element.requestFullscreen();
        }
        else if (element.mozRequestFullScreen) {
            element.mozRequestFullScreen();
        }
        else if (element.webkitRequestFullscreen) {
            element.webkitRequestFullscreen();
        }
        else if (element.msRequestFullscreen) {
            element.msRequestFullscreen();
        }*/
    }
 
    /*var v = new THREE.Vector3(1,0,1).normalize();
    console.log(v);
    var m = new THREE.Matrix4();
    m.makeRotationY(Math.PI/4);
    console.log(m);
    console.log(v.applyMatrix4(m));
    console.log(v.multiply(new THREE.Vector3(-1, 0, -1)));*/
</script>
</html>
展开内容

联系我们

一个人的力量不如两个人的,两个人的力量不如一群人的。欢迎加入大家庭一起共同学习,共同进步。

查看更多