今天要來將 Cannon.js 的物理效果導入到專案中,讓場景中的物體都具有物理剛體效果,並且會做專案整合的工程,將前面所有開發的功能補齊,方便後續的開發。
Photo by ian dooley on Unsplash
這是本系列第 23 篇,如果還沒看過第 22 篇可以點以下連結前往:
用 Three.js 來當個創世神 (22):專案實作#11 - 使用 PointerLockControls 實現射擊遊戲視角 Part.2
今天的工程比較繁瑣一點,主要是要參考 Cannon.js 官方射擊範例將 Cannon.js 的物理效果導入到專案中,目前只讓玩家本體、地面、隨機產生的箱子具有剛體效果,之後會繼續剛體化苦力怕及加上射擊效果。
請參考完整原始碼及成果展示,苦力怕變巨人啦!有人也覺得彩色的箱子搭雪景很像聖誕節禮物嗎?
因為功能越來越多,程式碼也越來越長,在導入 Cannon.js 之前,做了一番整理,時間關係目前先暫時用 <script> tag
切成各個模組:
index.js
:整合所有模組,並負責處理所有功能的初始化及 render 相關工作。creeperModule
:產生苦力怕物件的 class module。creeperAnimationModule
:苦力怕動畫相關。explosionModule
:爆炸效果的 class module。pointsSceneModule
:粒子特效場景。pointerLockControlsModule
:鼠標鎖定視角控制器相關。datGuiControlModule
:dat.GUI 相關。另外也將靜態檔及函式庫下載至專案中整理如上圖。
今天主要的 Cannon.js 導入工作會在 index.js
中實作。
function initCannon() {
...
// 解算器設定
const solver = new CANNON.GSSolver()
solver.iterations = 7 // 解算迭代次數,越高越精確,一般設定 7 即可
solver.tolerance = 0.1 // 解算容許誤差值
const split = true
if (split) world.solver = new CANNON.SplitSolver(solver)
else world.solver = solver
// 接觸材質相關設定(摩擦力、恢復係數)
world.defaultContactMaterial.contactEquationStiffness = 1e9
world.defaultContactMaterial.contactEquationRelaxation = 4
physicsMaterial = new CANNON.Material('slipperyMaterial')
const physicsContactMaterial = new CANNON.ContactMaterial(
physicsMaterial,
physicsMaterial,
0.0, // 摩擦力
0.3 // 恢復係數
)
world.addContactMaterial(physicsContactMaterial)
...
}
這裡出現了幾個比較陌生的新東西,稍微根據官網文件來研究一下:
The physics world has a solver that is used to resolve constraints.
用來計算物理約束的工具。
A constraint is a physical connection that removes degrees of freedom from bodies. In 3D, a body has 6 degrees of freedom (three translation coordinates and three rotation coordinates).
每個剛體都具有六個自由度(degrees of freedom),分別是三軸的平移及旋轉,當某種物理連結的行為造成某剛體失去自由度,則稱此行為為約束。
舉例而言如上圖,可以說我們將門板「約束」在牆上,因為這個「約束」,門板只剩下沿著門樞旋轉的這個自由度。
而關於解算器、接觸材質的設定,只能大概理解用途,但目前都依照與範例相同的設定。
而關於鼠標控制器的部分,觀察了一下範例的做法,發現 Cannon.js 的作者自己改寫了函式庫(下載連結),讓控制器可以傳入剛體並具有物理效果,因此我們在 initCannon()
的地方新增一個鼠標控制器的剛體:
function initCannon() {
...
// 鼠標控制器剛體
sphereShape = new CANNON.Sphere(1.5)
sphereBody = new CANNON.Body({ mass: 5 })
sphereBody.addShape(sphereShape)
sphereBody.position.set(0, 5, 0)
sphereBody.linearDamping = 0.9
world.addBody(sphereBody)
}
而前面幾篇關於鼠標控制器的設定都整理至 pointerLockControlsModule.js
中的 initPointerLockControls()
中,並傳入上面宣告的剛體:
function initPointerLockControls() {
// 鼠標控制器初始化
controls = new PointerLockControls(camera, sphereBody)
...
}
function createBoxes(count) {
// Add boxes
const halfExtents = new CANNON.Vec3(1, 1, 1)
const boxShape = new CANNON.Box(halfExtents)
const boxGeometry = new THREE.BoxGeometry(
halfExtents.x * 2,
halfExtents.y * 2,
halfExtents.z * 2
)
for (let i = 0; i < count; i++) {
const x = (Math.random() - 0.5) * 30
const y = 10 + (Math.random() - 0.5) * 1
const z = (Math.random() - 0.5) * 30
const boxBody = new CANNON.Body({ mass: 5 })
boxBody.addShape(boxShape)
const boxMaterial = new THREE.MeshLambertMaterial({
color: Math.random() * 0xffffff
})
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)
world.addBody(boxBody)
scene.add(boxMesh)
boxBody.position.set(x, y, z)
boxMesh.position.set(x, y, z)
boxMesh.castShadow = true
boxMesh.receiveShadow = true
boxes.push(boxBody)
boxMeshes.push(boxMesh)
}
}
這邊新增了一些隨機生成的箱子剛體,用來測試專案中的物理效果是否正確,跟前面「第 20 篇:Cannon.js 基本練習」的概念差不多,就是新增剛體模型、並利用網格模型產生畫面,最後在 render()
中隨著剛體的運動更新網格模型的位移與旋轉:
function render() {
requestAnimationFrame(render)
...
if (controls.enabled) {
world.step(dt)
// Update box mesh positions
for (let i = 0; i < boxes.length; i++) {
boxMeshes[i].position.copy(boxes[i].position)
boxMeshes[i].quaternion.copy(boxes[i].quaternion)
}
}
controls.update(Date.now() - time)
time = Date.now()
...
renderer.render(scene, camera)
}
在導入 Cannon.js 之前,因為有預感會需要加許多剛體相關的程式,而在前面二十幾天已經寫了許多功能,人物模型、動畫、下雪場景、爆炸、視角控制,全部都擠在一隻檔案裡肯定是會逐漸母湯,所以決定要來整理一下程式讓後續開發更順暢。
一想到要做模組化,印象中在工作上做專案開發時總是會將程式切出去並用 require
或 import
之類引入的寫法,稍微查了一下似乎要導入 RequireJS
或 Webpack
之類的工具,由於筆者之前沒有太多起專案的經驗對這塊並不熟悉,所以權衡之下暫時還是先用最原始的 <script> tag
來切割程式。
另外特別感謝 Gary 大大提供可以在 script tag 中使用
type="module"
的寫法(參考連結),之後也可以來試試。
透過這個專案也算是深刻體會到,為什麼要在起專案之前做好這些工具的基礎建設了,中間一度有發現似乎不太對勁,果然在最後整合的時候會碰到許多問題,之後應該利用這個機會在做優化時可以來仔細學習一下。
今天成功將 Cannon.js 導入到專案中,目前只讓玩家本體、地面、隨機產生的箱子具有剛體效果,原本想要連苦力怕一起剛體化,但還正在 try and error 的開發階段,另外官方範例中的射擊子彈看起來也不複雜,但因為他是使用比較舊版的 Three.js,所以還需要另外把 THREE.Projector
換成 raycaster
。
明天順利的話應該可以將苦力怕剛體化及完成射擊子彈效果,我們明天見!