THREE.JS開發《我的世界》(一)


  大學時一直很喜歡玩minecraft這一款游戲,畢業之后便沒有太多的時間玩游戲,但是對其的關注確一直沒有減少,當網易獲得我的世界代理權時,看到網易官網做的炫酷的3Dbanner。非常炫酷,於是萌生了做一個簡單的網頁版的世界的想法。

 

一,准備工作

1)庫/框架選型

THREE.JS

  要了解THREE.JS,首先要了解一下什么是WEBGL,WebGL(全寫Web Graphics Library)是一種3D繪圖協議,這種繪圖技術標准允許把JavaScript和OpenGL ES 2.0結合在一起,通過增加OpenGL ES 2.0的一個JavaScript綁定,WebGL可以為HTML5 Canvas提供硬件3D加速渲染,這樣Web開發人員就可以借助系統顯卡來在瀏覽器里更流暢地展示3D場景和模型了,還能創建復雜的導航和數據視覺化.

ES6
  這個不用多說

2)構建工具

WebPack
  WebPack是一個模塊打包工具,它可以分析你的項目結構,找到JavaScript模塊以及其它的一些瀏覽器不能直接運行的拓展語言(Scss,TypeScript等),並將其轉換和打包為合適的格式供瀏覽器使用。 

二,項目構建

Webpack4 + ES6

  環境搭建,可以看我之前的一篇博文,這里不再贅述。Webpack + ES6 最新環境搭建與配置

三,主要功能實現

  對於THREE.JS的一些基本知識本章不重點敘述,園子里很多博文寫得非常很清楚,大家自行查找。本文只敘述實現本節相關的代碼。以下連接是官方提供的一個簡單的在3D空間中生成立方體的例子,簡單的敘述了如何使用three.js的基本用法和怎么產生方塊。讀懂它對本例的理解會有非常大的幫助。

https://threejs.org/examples

1 )構建地面

  構建地面我們要做什么

1 > 在3D空間中以(0.0.0)為基准產生一串連續的數組作為地形坐標

  地面生成算法,這里引用了 Improved Noise reference implementation ,three官方例子 webgl_geometry_minecraft,參考THREE.JS的例子,本例做出了一些調整。由於作者這方面的基礎不是很好,不多做敘述。

2 > 在每個點的位置填充方塊

  細節1:只渲染可見的面 

 

  例如上圖A方塊,他的前后左右都有方塊,那他的前后左右的面都可以不用渲染,B方塊它的后面和右面都有方塊,它的后面和右面就不用渲染。只渲染需要的面,能夠縮小渲染時間,提高幀率。

  細節2:為暗面添加陰影

  如果一個方塊四周的方塊都比他低,那它是最亮的。如果他的四周的方塊都高於他,那他就會暗一些。如上圖的綠色區域,綠色區域就會相對較暗。下面是效果對比。

2)玩家視角

  我們所看到的畫面是攝像機拍攝的畫面,我們將攝像機當作玩家的眼睛,玩家使用鍵盤產生的動作來驅動攝像機移動,即可產生第一人稱的效果。

相關介紹:

Pointer Lock API

  它可以訪問原始的鼠標運動,把鼠標事件的目標鎖定到一個單獨的元素,這就消除了鼠標在一個單獨的方向上到底可以移動多遠這方面的限制,並從視圖中刪去光標。這樣就可以讓我們通過移動鼠標而不需要點擊任何按鈕就可以控制視角。

THREE.PointerLockControls

  THREE.JS提供了PointerLockControls控制器,實例化之后可以通過控制器對象的getObject()獲取到控制對象,設置控制對象的位置,在使用其提供的update函數實現人物的視角旋轉。

1 > 視角移動

  由Pointer Lock API與THREE.PointerLockControls完成,使用方式請參考本例代碼或者 https://developer.mozilla.org/zh-CN/docs/API/Pointer_Lock_API。

2 > 人物移動與物體碰撞
  通過監聽鍵盤的按鍵和控制相機的位置來控制人物的移動。

  找到物體的大致思路如1下圖

 

  鼠標在屏幕上點擊的時候,得到二維坐標p(x, y),再加上深度坐標的范圍(0, 1), 就可以形成兩個三位坐標A(x1, y1, 0), B(x2, y, 1), 由於它們的Z軸坐標是0和1,則轉變到投影坐標系的話,一定分別是前剪切平面上的點和后剪切平面上的點,也就是說,在投影坐標系中,A點一定在能看見的所有模型的最前面,B點一定在能看見的所有的模型的最后邊,將AB點連成線,AB線穿過的物體就是被點擊的物體。而 Three.js提供一個射線類Raycasting來拾取場景里面的物體。更方便的使用鼠標來操作3D場景。(在本例我們組成射線的兩個點是攝像機所在視點的相對位置與人物移動方向形成的射線)
詳細的官方文檔: Raycaster

new Raycaster( origin, direction, near, far );
origin — 光線投射的起點向量。 
direction — 光線投射的方向向量。 
near — 投射近點,用來限定返回比near要遠的結果。near不能為負數。缺省為0。 
far — 投射遠點,用來限定返回比far要近的結果。far不能比near要小。缺省為無窮大。

var ray = new THREE.Raycaster(newTHREE.Vector3(),newTHREE.Vector3().copy(this.directionY),0,51);
origin      初始化為(0,0,0),移動時在刷新
direction 初始化設置為Y方向,防止無限下墜,
near       設置為0
far          設置為51 (半個方塊 + 1)

  通過獲取到控制器對象的位置(相機的位置),將視線位置的Y值-200,就可以獲取到史蒂夫腳方塊的位置,如果腳的位置到地面的位置小於等於50(方塊寬度的一半),則說明史蒂夫的腳碰到的地面,就停止相機的下墜。對於碰撞也是相同的做法,如果腳方塊與移動方向前50PX內無方塊,則可以移動,否則停止。移動的方向我們通過攝像頭的方向(史蒂夫在三維空間視覺方向的矢量)和按鍵來確定,如果往前移動,那么檢測的方向就是視覺方向,如果往左移動,則需要將矢量逆時針旋轉90度后作為檢測的方向。

 

 控制器核心代碼如下:

update() {
        //判斷鼠標是否鎖定
        if (this.controlsEnabled === true) {

            //判斷鍵盤移動方向
            this.direction.z = Number(this.moveForward) - Number(this.moveBackward);
            this.direction.x = Number(this.moveLeft) - Number(this.moveRight);
            this.direction.normalize(); // this ensures consistent movements in all directions

            //用於檢測地面
            this.raycaster.ray.origin.copy(this.controls.getObject().position);

            //相機距離地面的相對高度
            this.raycaster.ray.origin.y -= this.height;
            let intersections = this.raycaster.intersectObjects([this.load]);

            //將攝像頭面對的相對方向置入cameradir變量,用於檢測障礙物.
            this.controls.getObject().getWorldDirection(this.cameradir);

            let intersections2 = [], onWard = false, dir = new THREE.Vector3();

            //確認移動的方向,以便檢測障礙物
            if (this.direction.z > 0) {
                //W
                dir.copy(this.cameradir).negate();
            } else if (this.direction.z < 0) {//
                //S
                dir.copy(this.cameradir);
            }
            if (this.direction.x > 0) {
                //A
                dir.copy(this.cameradir).applyAxisAngle(this.directionY, 90.0);
            } else if (this.direction.x < 0) {
                //D
                dir.copy(this.cameradir).applyAxisAngle(this.directionY, -90.0);
            }

            //用於檢測障礙物
            if (this.direction.z != 0 || this.direction.x != 0) {
                this.raycaster.ray.direction.copy(dir);
                intersections2 = this.raycaster.intersectObjects([this.load]);
                this.raycaster.ray.direction.copy(this.directionY);
            }

            //是否碰撞標志位
            onWard = intersections2.length > 0;

            //是否下墜標識位
            let onObject = intersections.length > 0;

            //一幀的時間 大概0.016S
            let time = performance.now();
            let delta = (time - this.prevTime) / 1000;
            //delta = 0.016; 調試時使用此項
            //以下為了保證有過渡效果
            this.velocity.x -= this.velocity.x * 10 * delta;
            this.velocity.z -= this.velocity.z * 10 * delta;
            this.velocity.y -= 9.8 * this.downSpeed * delta; // 100.0 = mass

            //人物移動距離計算
            if (this.moveForward || this.moveBackward) {
                this.velocity.z -= this.direction.z * this.moveSpeed * delta;
            }
            if (this.moveLeft || this.moveRight) {
                this.velocity.x -= this.direction.x * this.moveSpeed * delta;
            }

            //確保人物不下落
            if (onObject === true) {
                this.velocity.y = Math.max(0, this.velocity.y);
                this.canJump = true;
            }

            //確保人物碰撞停止
            if (onWard === true) {
                if ((this.direction.x != 0)) {
                    this.velocity.x = 0;
                }
                if ((this.direction.z != 0)) {
                    this.velocity.z = 0;
                }
            }

            //人物移動
            this.controls.getObject().translateX(this.velocity.x * delta);
            this.controls.getObject().translateY(this.velocity.y * delta);
            this.controls.getObject().translateZ(this.velocity.z * delta);

            this.prevTime = time;

        }
    }

代碼下載地址:https://github.com/sincw/sinwProject/tree/master/MineCraftWeb

   這樣就能夠完成一個基礎的我的世界了,請期待后續。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM