大家好,我是秋風,在上一篇 中說到了 Three.js 系列的目標以及寶可夢遊戲,那麼今天就來通過 Three.js 來談談關於遊戲中的視角跟隨問題。相信我的讀者都或多或少玩一些遊戲,例如王者榮耀、絕地求生、寶可夢、塞爾達、原神之類的遊戲。那麼你知道他們分別是什麼視角的遊戲麼?你知道第一人稱視角和第三人稱視角的差異麼?通過程式碼我們怎麼能實現這樣的效果呢?如果你對以上問題好奇,並且不能完全回答。那麼請跟隨著我一起往下看吧。
視角講解
首先我們先來看看第一人稱視角、第三人稱視角的概念。其實對於我們而言 第一人稱 和 第三人稱,是非常熟悉的,第一人稱就是以自己的口吻講述一件事,例如自傳都是以這種形式抒寫,第三人稱則是以旁觀者,例如很多小說,都是以他(xxx)來展開式將的,觀眾則是以上帝視角看著這整個故事。對應的第一人稱視角、第三人稱視角也是相同的概念,只不過是視覺上面。那麼他們各自有上面區別呢?第一人稱視角的有點是可以給玩家帶來最大限度的沉浸感,從第一人稱視角“我”去觀察場景和畫面,可以讓玩家更加細緻地感受到其中的細節,最常見的就是類似絕地求生、極品飛車之類的。
而第一人稱視角也有他的侷限性。玩家的視野受限,無法看到更廣闊的的視野。另一個就是第一人稱視角會給玩家帶來“3D眩暈感”。當反應速度更不上鏡頭速度的時候會造成眩暈感。那麼第三人稱視角呢?他的優勢就是自由,視野開闊,人物移動和視角是分開的,一個用來操作人物前進方向,另一個則是用來操控視野方向。
它的劣勢就是無法很好的聚焦區域性,容易錯過細節。但是總的來說,目前大多數遊戲都提供了兩種視角的切換來滿足不同的情形。例如絕對求生中平時走路用第三人稱視角跟隨移動,開槍的時候一般用第一人稱視角。好了,到目前為主我們已經知道了第一人稱視角、第三人稱視角各自概念、區別。那麼我們接下來以第三人稱視角為例,展開分析我們該如何實現這樣的一個效果呢?(第三人稱的編寫好後,稍加修改就可以變成第一人稱,因此以更加複雜的第三人稱為例)把大象放入冰箱需要幾步?三步!開啟冰箱,把大象放進冰箱,關上冰箱。顯然如果真的要把大象放進冰箱是很難的事情,但是從巨集觀角度來看,就是三個步驟。因此我們也將實現第三人稱視角這個功能分成三步:
步驟拆分
以下的步驟拆分不會包含任何程式碼,請放心使用:1.人物如何運動我們都知道在物理真實的世界中,我們運動起來是靠我們雙腿,邁開就動起來了。那這個過程從更巨集觀的角度來看是怎麼樣的呢?其實如果從地球外,從一個更遠的角度來看,我們做運動更像是一個個平移變化。相同地,我們在計算機中來表示運動也就是運用了平移變化。平移變化詳細大家以前都比較熟悉,如果現在不熟悉了呢,也沒有什麼關係,先看下面的座標軸。(小方塊的邊長是1)
小方塊從A1位置移動到位置A2就是平移變化,如果用數學表示式來表示的話就是
上面是什麼意思呢?就是說我們讓小方塊中所有的小點的 x 值都加2,而 y 的值不變。我們隨意取一些值來驗證一下。例如A1位置小方塊,左下角是 (0,0), 通過以上變化,就變成了 (2, 0),我們來A2中看小方塊新的位置就是 (2, 0);再用右上角的 (1,1) 代入,發現就變成了(3,1),和我們真實移動到的位置也是一樣的。所以上面的式子沒有什麼問題。但是後來呢,大家覺得像上面那樣的式子用來表示稍微有點不夠通用。至於這裡為什麼說不夠通用,在後面的系列文章中會詳細講解,因為還涉及到了其他變化,例如旋轉、縮放,他們都可以用一個矩陣來進行描述,因此如果平移也能夠用矩陣的方式來表達,那麼整個問題就變得簡單了,也就是說:運動變化 = 矩陣變化我們來看看把最開始的式子變成矩陣是什麼樣子的:
可以簡單講解一下右邊這個矩陣是怎麼來的
左上角的這個部分稱為單位矩陣,後面的 2 0 則就是我們需要的平移變化,至於為什麼從2維變成了3維,則是因為引入了一個齊次矩陣的概念。同樣的原理,類比到 3維,我們就需要用到4維矩陣。所以說,我們通過一系列的例子,最終想要得到的一個結論就是,所有的運動都是矩陣變化。
2.鏡頭朝向人物我們都知道,在現實世界中我們眼睛看出去的視野是有限的,在電腦中也是一樣的。假設在電腦中我們的視野是 3 * 3 的方格,我們還是以之前座標軸舉例子,黃色區域是我們的視野可見區域:
現在我們讓小塊往右移動3個單位,再網上移動1個單位。
這個時候我們會發現,我們的視野內已經看不到這個小塊了。試想一下,我們正在玩一個射擊遊戲,敵人在眼前移動,我們為了找到它會在怎麼辦?沒錯,我們會旋轉我們的腦袋,從而使得敵人暴露在我們的視野內。就像這樣:
這下就把敵人鎖定住了,能夠始終讓人物出現在我們的視野內並且保持相對靜止。3.鏡頭與人物同距光有鏡頭朝向人物還不夠,我們還得讓我們的鏡頭和人物同距。為什麼這麼說呢,首先還是我們座標軸的例子,但是這次我們將擴充一個z軸:然後我們看看正常下的平面截圖
截圖:
現在我們將我們的小塊往-Z 移動1個單位:
截圖:
這個時候我們發現這個小方塊變小了,並且隨著小方塊往 -z方向移動的越多,我們看到的小塊會越來越小。這個時候我們明明沒有改變我們的視角,但是還是無法很好的跟蹤小塊。因此我們需要移動為我們視角的位置,當我們看不清一個遠處的路標的時候,我們會怎麼辦?沒錯,湊近點!
截圖:
完美!現在我們通過三個方向的講解,將如果實現一個第三人稱視角的功能從理論上面實現了!
搞程式碼
接下來我們只需要按照我們的以上的理論,來實現程式碼就好了,程式碼無法就是我們用另一種語言的實現方式,知道了原理都是非常簡單的。1.初始化畫布場景
<canvas class="webgl"></canvas>
...
<script>
// 建立場景
const scene = new THREE.Scene()
// 加入相機
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height);
camera.position.y = 6;
camera.position.z = 18;
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true; // 設定阻尼,需要在 update 呼叫
scene.add(camera);
// 渲染
const renderer = new THREE.WebGLRenderer({
canvas
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.render(scene, camera);
</script>
場景、相機、渲染器是一些比較固定的東西,這一節不主要進行講解,可以理解為我們專案初始化的時候一些必備的語句。這個時候我們開啟頁面,是黑乎乎的一片,為了美觀,我給整個場景加上一個地板。
// 設定地板
const geometry = new THREE.PlaneGeometry(1000, 1000, 1, 1);
// 地板貼圖
const floorTexture = new THREE.ImageUtils.loadTexture( '12.jpeg' );
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set( 10, 10 );
// 地板材質
const floorMaterial = new THREE.MeshBasicMaterial({
map: floorTexture,
side: THREE.DoubleSide
});
const floor = new THREE.Mesh(geometry, floorMaterial);
// 設定地板位置
floor.position.y = -1.5;
floor.rotation.x = - Math.PI / 2;
scene.add(floor);
`
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa8cea3705594d10a7e01e7225283a78~tplv-k3u1fbpfcp-zoom-1.image)
這個時候畫面還不錯\~2.人物運動根據理論,我們需要加入一個人物,這裡為了方便,也還是加入一個小方塊為主:
// 小滑塊
const boxgeometry = new THREE.BoxGeometry(1, 1, 1);
const boxMaterials = [];
for (let i = 0; i < 6; i++) {
const boxMaterial = new THREE.MeshBasicMaterial({
color: Math.random() * 0xffffff,
});
boxMaterials.push(boxMaterial);
}
// 小塊
const box = new THREE.Mesh(boxgeometry, boxMaterials);
box.position.y = 1;
box.position.z = 8;
scene.add(box);
為了好看,我給小塊加了六面不同的顏色。
雖然看起來還是有點簡陋,但是俗話說高階的食材往往只需要最樸素的烹飪方式。小塊雖小,但是五臟俱全。現在我們渲染出了小塊後,要做的事情就是繫結快捷鍵。
對應的程式碼:
// 控制程式碼
const keyboard = new THREEx.KeyboardState();
const clock = new THREE.Clock();
const tick = () => {
const delta = clock.getDelta();
const moveDistance = 5 * delta;
const rotateAngle = Math.PI / 2 * delta;
if (keyboard.pressed("down"))
box.translateZ(moveDistance);
if (keyboard.pressed("up"))
box.translateZ(-moveDistance);
if (keyboard.pressed("left"))
box.translateX(-moveDistance);
if (keyboard.pressed("right"))
box.translateX(moveDistance);
if (keyboard.pressed("w"))
box.rotateOnAxis( new THREE.Vector3(1,0,0), rotateAngle);
if (keyboard.pressed("s"))
box.rotateOnAxis( new THREE.Vector3(1,0,0), -rotateAngle);
if (keyboard.pressed("a"))
box.rotateOnAxis( new THREE.Vector3(0,1,0), rotateAngle);
if (keyboard.pressed("d"))
box.rotateOnAxis( new THREE.Vector3(0,1,0), -rotateAngle);
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
tick();
這裡解釋一下 translateZ、translateX,這倆函式就是字面意思,往 z 軸 和 x 軸移動,如果想要往前,就往 -z 軸移動,如果是往 左就是往 -x 軸移動。clock.getDelta ()
是什麼意思呢?簡單說.getDelta ()
方法的功能就是獲得前後兩次執行該方法的時間間隔。例如我們想要在1秒內往前移動5個單位,但是直接移動肯定比較生硬,因此我們想加入動畫。我們知道為了實現流暢的動畫,一般通過瀏覽器的APIrequestAnimationFrame
實現,瀏覽器會控制渲染頻率,一般效能理想的情況下,每秒s
渲染60次左右,在實際的專案中,如果需要渲染的場景比較複雜,一般都會低於60,也就是渲染的兩幀時間間隔大於16.67ms。因此為了移動這5個單位,我們將每一幀該移動的距離,拆分到了這 60次渲染中。最後來說說 rotateOnAxios
,這個主要就是用來控制 小盒子的旋轉。
.rotateOnWorldAxis ( axis : Vector3, angle : Float ) : this axis -- 一個在世界空間中的標準化向量。
angle -- 角度,以弧度來表示。
3.相機與人物同步回顧理論部分,我們最後一個步驟就是想要讓相機(人眼)和物體保持相對靜止的,也就是距離不變。
const tick = () => {
...
const relativeCameraOffset = new THREE.Vector3(0, 5, 10);
const cameraOffset = relativeCameraOffset.applyMatrix4( box.matrixWorld );
camera.position.x = cameraOffset.x;
camera.position.y = cameraOffset.y;
camera.position.z = cameraOffset.z;
// 始終讓相機看向物體
controls.target = box.position;
...
}
這裡有個比較核心的點就是 relativeCameraOffset.applyMatrix4( box.matrixWorld );
其實這個我們在理論部分說過了,因為我們的物體移動的底層原理就是做矩陣變化,那麼想要讓相機(人眼)和物體的距離不變,我們只需要讓相機(人眼)和物體做相同的變化。而在 Three.js 中物體所有的自身變化都記錄在 .matrix
裡面,只要外部的場景不發生變化,那麼.matrixWorld
就等於 .matrix
。而applyMatrix4
的意思就是相乘的意思。
效果演示
這樣我就最終實現了整個功能!我們下期見!
原始碼地址:https://github.com/hua1995116...
結語
❤️關注+點贊+收藏+評論+轉發❤️,原創不易,鼓勵筆者創作更好的文章
關注公眾號秋風的筆記
,一個專注於前端面試、工程化、開源的前端公眾號