严格来说这一部分不是讲的three.js,不过一路写过来大家是不是发现前面虽然实现的键盘控制,但是控制起来非常不方便,问题多多的有那么我们怎么解决呢?
我们首先解决一个比较简单的部分——实现按多个键能斜着走。
做过游戏开发的应该知道,之前我们的键盘函数就是有毛病的。我们不应该简单粗暴的检测onkeydown事件后改变坐标,而是检测到onkeydown后给对应方向一个速度,检测到onkeyup后再把这个方向的速度去掉,坐标的改变由一个专门的更新函数统一计算。
根据这个思路,我这里写了一个闭包并将数据放入其中以防止外部修改(实际操作中应该根据实际情况而定,建议绑定到相机对象上)(为了避免过多的闭包,我这里共用了之前渲染的闭包,并且把鼠标控制的也拿进来了。天啊我之前写这些代码的时候为什么想着要在这些地方加上闭包,其实有些东西不用闭包的,不过影响不是很大,这个的优劣就留给大家自己思考选择吧)
- var evfuns=(function(obj){
- var move={x:0,y:0};
- var last_mouse_xy=null;
- return {
- 'test':123,
- 'onkeydown':function(ev){
- switch(ev.keyCode){
- case 65:
- move.x=-1;
- break;
- case 68:
- move.x=1;
- break;
- case 87:
- move.y=1;
- break;
- case 83:
- move.y=-1;
- break;
- case 66:
- obj.camera.position.z+=10;
- }
- },
- 'onkeyup':function(ev){
- switch(ev.keyCode){
- case 65:
- case 68:
- move.x=0;
- break;
- case 87:
- case 83:
- move.y=0;
- break;
- }
- },
- 'onmousemove':function(ev){
- if(!ev.x) ev.x=ev.layerX;
- if(!ev.y) ev.y=ev.layerY;
- if(last_mouse_xy){
- if(Math.abs(ev.x-last_mouse_xy.x)<9&&Math.abs(ev.y-last_mouse_xy.y)<9){//这样可以有效防止因为鼠标移出div后从其他点进入产生的问题
- obj.camera.rotation.y-=(ev.x-last_mouse_xy.x)*0.01;
- obj.camera.rotation.x-=(ev.y-last_mouse_xy.y)*0.01;
- if(obj.camera.rotation.x>Math.PI/2)obj.camera.rotation.x=Math.PI/2;
- if(obj.camera.rotation.x<-Math.PI/2)obj.camera.rotation.x=-Math.PI/2;
- }
- }
- last_mouse_xy={x:ev.x,y:ev.y};
- },
- 'render':function(){
- obj.renderer.render(obj.scene, obj.camera);//渲染
- requestAnimationFrame(obj.rander);
- obj.stats.update();
- },
- 'onupdate':function(){
- //定时根据速度更新坐标
- }
- }
- })(this);
- document.onkeydown=evfuns['onkeydown'];
- document.onkeyup=evfuns['onkeyup'];
- this.dom.onmousemove=evfuns['onmousemove'];
- this.rander=evfuns['render'];
- this.rander();
好了接下来的问题大家应该看到了,onupdate函数还空着呢,它的作用是更新相机(或其他对象)的坐标,为了一次到位解决按键方向在视角旋转后方向不正确的问题我们先空着,研究下这个坐标及角度的问题。
之前移动方向不正确是因为鼠标控制导致摄像头转了一个方向后,摄像机的前后左右不再和原坐标轴相重合。相信你们很快就想到了如何解决了,没错,就是加入sin和cos计算在两个坐标轴上的分量。不过这个分量不是那么容易找的,可以参考下下面的图:
我们不难算出,z方向增量因为v0xsin-v0ycos,而x方向增量则为v0xcos+v0ysin,不过还有一点需要注意的是,我们使用的是camera.rotation.y得到的角度,它是与z轴负半轴逆时针方向所的角,即图中绿色的部分,所以我们得到的角度需要用2*Math.PI去减这个角度才能得到正确的θ,当然喽,方法不止一个,比如之前我试过一些用起来比较方便的办法,但是这些都比这种没有经过三角函数变换的容易理解。
根据这个思路我们这样写onupdate函数:
- var SIN=Math.sin(2*Math.PI-obj.camera.rotation.y);
- var COS=Math.cos(2*Math.PI-obj.camera.rotation.y);
- obj.camera.position.x+=move.x*COS+move.y*SIN;
- obj.camera.position.z+=move.x*SIN-move.y*COS;
可是光有函数还不行,我们还要调用它,我们的思路是定时调用(将来可能还要加入重力和跳的呢,所以定时更新是有必要的),那么就写一个定时器,每隔10毫秒调用一次(具体时间可根据实际情况调整,但不易过长,应当尽量小于或等于屏幕刷新频率,但也应考虑电脑能不能带的得动)吧:
- setInterval(evfuns['onupdate'],10);
把这句话加在this.rander();
后面再用浏览器打开试试,是不是好用多呢?至此,我们的工作就完成了90%,不过鼠标的处理还是让人不舒服,移着移着就出去了。由于安全问题,浏览器是不会让我们将鼠标位置修改到屏幕中心的,我们的方法只能是让它在靠近边界的时候即使不移动鼠标也能往对应的方向移动,且越近速度越快(用过笔记本触控板拖动窗口之类的应该知道吧)。
既然如此我们就在onupdate函数中增加一下判断然后进行旋转。w我们在onupda函数中加入了如下代码:
- if(last_mouse_xy.x<100) obj.camera.rotation.y+=(100-last_mouse_xy.x)*0.001;
- if(last_mouse_xy.x+100>parseInt(obj.dom.offsetWidth)) obj.camera.rotation.y-=(100+last_mouse_xy.x-parseInt(obj.dom.offsetWidth))*0.001;
- if(last_mouse_xy.y<100) obj.camera.rotation.x+=(100-last_mouse_xy.y)*0.001;
- if(last_mouse_xy.y+100>parseInt(obj.dom.offsetHeight)) obj.camera.rotation.x-=(100+last_mouse_xy.y-parseInt(obj.dom.offsetHeight))*0.001;
- ismouseok();
咦,ismouseok是什么?之前我们不是有检测过鼠标上下移动时“脑袋”是否仰过头了吗?现在同样需要检测,所以就把它打包成了一个函数。用浏览器运行一下??唉唉,我也不知道你们是不是会出各种各样的问题而没有问题,因为浏览器兼容鼠标坐标检测有差异,之前把键盘事件绑定到div上也在一些浏览器上出了问题。我我这里修改鼠标函数如下:
- var x=ev.layerX;
- var y=ev.layerY;
- if(last_mouse_xy){
- if(Math.abs(x-last_mouse_xy.x)<9&&Math.abs(y-last_mouse_xy.y)<9){//这样可以有效防止因为鼠标移出div后从其他点进入产生的问题
- obj.camera.rotation.y-=(x-last_mouse_xy.x)*0.01;
- obj.camera.rotation.x-=(y-last_mouse_xy.y)*0.01;
- }
- }
- ismouseok();
- last_mouse_xy={x:x,y:y};
这样问题应该就能解决,如果还是不行的话再对元素进行定位试试?建议用“position: relative;”,一般来说这是不会影响原有排版的定位(也不是一定的啦),还有一种可能就是浏览器不支持layerX之类的,那就用offsetX试试?你们可以自己考虑做一个判断,这里因为时间问题我就不详细测试了,只要解决这几个问题应该就正常了,要是还是不行的话,也许考虑换一个浏览器吧,或者自己探讨一下。
最后附上本次修改后的完整代码:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>webgl互交测试3</title>
- <style>
- *{
- padding: 0;
- margin: 0;
- position: relative;
- }
- #b{
- width: 900px;
- height: 500px;
- background: #000;
- margin: 20px auto;
- }
- </style>
- </head>
- <body>
- <div id="b">
- <div id="canvas-frame"></div>
- </div>
- <script src="three-js/build/three.js" data-ke-src="https://raw.github.com/mrdoob/three.js/master/build/three.js"></script>
- <script src="three-js/examples/js/libs/stats.min.js"></script>
- <script>
- function mythree(id){
- this.dom=document.getElementById(id);
- this.scene = new THREE.Scene();//场景
- this.camera=new THREE.PerspectiveCamera(75, this.dom.offsetWidth / this.dom.offsetHeight, 0.1, 1000);
- this.camera.position.set(0,200,0);
- this.renderer = new THREE.WebGLRenderer();//渲染器
- this.renderer.setSize(this.dom.offsetWidth,this.dom.offsetHeight);//设置渲染器大小
- this.renderer.setClearColorHex(0x000000, 1);//设置渲染器颜色
- this.dom.appendChild(this.renderer.domElement);
- this.dom.style.position="relative";
- this.stats = new Stats();
- this.stats.domElement.style.position = 'absolute';
- this.stats.domElement.style.left = '0px';
- this.stats.domElement.style.top = '0px';
- this.dom.appendChild(this.stats.domElement);
- this.camera.eulerOrder='YXZ';//修改旋转顺序 默认为xyz
- //这一步可以使得绕x轴旋转时参考已经旋转的y轴的量(相当于让x轴也随y轴旋转了),能减少后续工作
- //不做这一步还会出现很多问题
- //把这些函数(事件)写到一个闭包中,避免过多闭包
- //把this传入闭包 使得他们能通过obj来代替this
- //键盘函数分成两个部分,记录按键是否按下,这样同时按两个键也能被记录下来,在渲染时再去做移动(这样就可以斜着走了)
- var evfuns=(function(obj){
- var move={x:0,y:0};
- var last_mouse_xy=null;
- var ismouseok=function(){
- if(obj.camera.rotation.x>Math.PI/2)obj.camera.rotation.x=Math.PI/2;
- if(obj.camera.rotation.x<-Math.PI/2)obj.camera.rotation.x=-Math.PI/2;
- }
- return {
- 'test':123,
- 'onkeydown':function(ev){
- switch(ev.keyCode){
- case 65:
- move.x=-1;
- break;
- case 68:
- move.x=1;
- break;
- case 87:
- move.y=1;
- break;
- case 83:
- move.y=-1;
- break;
- case 66:
- obj.camera.position.z+=10;
- }
- },
- 'onkeyup':function(ev){
- switch(ev.keyCode){
- case 65:
- case 68:
- move.x=0;
- break;
- case 87:
- case 83:
- move.y=0;
- break;
- }
- },
- 'onmousemove':function(ev){
- var x=ev.layerX;
- var y=ev.layerY;
- if(last_mouse_xy){
- if(Math.abs(x-last_mouse_xy.x)<9&&Math.abs(y-last_mouse_xy.y)<9){//这样可以有效防止因为鼠标移出div后从其他点进入产生的问题
- obj.camera.rotation.y-=(x-last_mouse_xy.x)*0.01;
- obj.camera.rotation.x-=(y-last_mouse_xy.y)*0.01;
- }
- }
- ismouseok();
- last_mouse_xy={x:x,y:y};
- },
- 'render':function(){
- obj.renderer.render(obj.scene, obj.camera);//渲染
- requestAnimationFrame(obj.rander);
- obj.stats.update();
- },
- 'onupdate':function(){
- var SIN=Math.sin(2*Math.PI-obj.camera.rotation.y);
- var COS=Math.cos(2*Math.PI-obj.camera.rotation.y);
- obj.camera.position.x+=move.x*COS+move.y*SIN;
- obj.camera.position.z+=move.x*SIN-move.y*COS;
- if(last_mouse_xy.x<100) obj.camera.rotation.y+=(100-last_mouse_xy.x)*0.001;
- if(last_mouse_xy.x+100>parseInt(obj.dom.offsetWidth)) obj.camera.rotation.y-=(100+last_mouse_xy.x-parseInt(obj.dom.offsetWidth))*0.001;
- if(last_mouse_xy.y<100) obj.camera.rotation.x+=(100-last_mouse_xy.y)*0.001;
- if(last_mouse_xy.y+100>parseInt(obj.dom.offsetHeight)) obj.camera.rotation.x-=(100+last_mouse_xy.y-parseInt(obj.dom.offsetHeight))*0.001;
- ismouseok();
- }
- }
- })(this);
- document.onkeydown=evfuns['onkeydown'];
- document.onkeyup=evfuns['onkeyup'];
- this.dom.onmousemove=evfuns['onmousemove'];
- this.rander=evfuns['render'];
- this.rander();
- setInterval(evfuns['onupdate'],10);
- }
- var mt=new mythree('b');
- var light = new THREE.DirectionalLight(0xffffff,1.0,0); //设置平行光源
- light.position.set(200,100,150); //设置光源向量
- mt.scene.add(light); //追加光源到场景
- mt.scene.add(new THREE.AmbientLight(0x222222))
- var ca=new THREE.Mesh(
- new THREE.CubeGeometry(40,100,40),
- new THREE.MeshLambertMaterial({color: 0x0088ff})
- );//创建实物
- ca.position.set(0,0,0);
- mt.scene.add(ca);
- c=new THREE.Mesh(
- new THREE.CubeGeometry(1,400,400),
- new THREE.MeshLambertMaterial({color: 0x0088ff})
- );//创建实物
- c.position.set(-200,200,0);
- mt.scene.add(c);
- c=new THREE.Mesh(
- new THREE.CubeGeometry(400,1,400),
- new THREE.MeshLambertMaterial({color: 0x0088ff})
- );//创建实物
- c.position.set(0,-1,0);
- mt.scene.add(c);
- c=new THREE.Mesh(
- new THREE.CubeGeometry(400,400,1),
- new THREE.MeshLambertMaterial({color: 0x0088ff})
- );//创建实物
- c.position.set(0,200,-200);
- mt.scene.add(c);
- </script>
- </body>
- </html>