iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 24
2
Modern Web

用 Three.js 來當個創世神系列 第 24

用 Three.js 來當個創世神 (23):專案實作#12 - 導入 Cannon.js 物理效果

今天要來將 Cannon.js 的物理效果導入到專案中,讓場景中的物體都具有物理剛體效果,並且會做專案整合的工程,將前面所有開發的功能補齊,方便後續的開發。

23_front

Photo by ian dooley on Unsplash


這是本系列第 23 篇,如果還沒看過第 22 篇可以點以下連結前往:

用 Three.js 來當個創世神 (22):專案實作#11 - 使用 PointerLockControls 實現射擊遊戲視角 Part.2


今日目標

今天的工程比較繁瑣一點,主要是要參考 Cannon.js 官方射擊範例將 Cannon.js 的物理效果導入到專案中,目前只讓玩家本體、地面、隨機產生的箱子具有剛體效果,之後會繼續剛體化苦力怕及加上射擊效果。

23_demo

請參考完整原始碼成果展示,苦力怕變巨人啦!有人也覺得彩色的箱子搭雪景很像聖誕節禮物嗎?

 

專案檔案結構調整

因為功能越來越多,程式碼也越來越長,在導入 Cannon.js 之前,做了一番整理,時間關係目前先暫時用 <script> tag 切成各個模組:

23_file

  • index.js:整合所有模組,並負責處理所有功能的初始化及 render 相關工作。
  • creeperModule:產生苦力怕物件的 class module。
  • creeperAnimationModule:苦力怕動畫相關。
  • explosionModule:爆炸效果的 class module。
  • pointsSceneModule:粒子特效場景。
  • pointerLockControlsModule:鼠標鎖定視角控制器相關。
  • datGuiControlModule:dat.GUI 相關。

另外也將靜態檔及函式庫下載至專案中整理如上圖。

 

Cannon.js 導入

今天主要的 Cannon.js 導入工作會在 index.js 中實作。

1. 解算器、接觸材質設定

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)
  ...
}

這裡出現了幾個比較陌生的新東西,稍微根據官網文件來研究一下:

解算器(solver)

The physics world has a solver that is used to resolve constraints.

用來計算物理約束的工具。

約束(Constraint)

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),分別是三軸的平移及旋轉,當某種物理連結的行為造成某剛體失去自由度,則稱此行為為約束

23_door

Photo by Behi on Unsplash

舉例而言如上圖,可以說我們將門板「約束」在牆上,因為這個「約束」,門板只剩下沿著門樞旋轉的這個自由度。

而關於解算器、接觸材質的設定,只能大概理解用途,但目前都依照與範例相同的設定。

 

2. 鼠標鎖定控制器(玩家本體)剛體化及設定

而關於鼠標控制器的部分,觀察了一下範例的做法,發現 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)
  ...
}

 

3. 其他物體剛體化

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 之前,因為有預感會需要加許多剛體相關的程式,而在前面二十幾天已經寫了許多功能,人物模型、動畫、下雪場景、爆炸、視角控制,全部都擠在一隻檔案裡肯定是會逐漸母湯,所以決定要來整理一下程式讓後續開發更順暢。

一想到要做模組化,印象中在工作上做專案開發時總是會將程式切出去並用 requireimport 之類引入的寫法,稍微查了一下似乎要導入 RequireJSWebpack 之類的工具,由於筆者之前沒有太多起專案的經驗對這塊並不熟悉,所以權衡之下暫時還是先用最原始的 <script> tag 來切割程式。

另外特別感謝 Gary 大大提供可以在 script tag 中使用 type="module" 的寫法(參考連結),之後也可以來試試。

透過這個專案也算是深刻體會到,為什麼要在起專案之前做好這些工具的基礎建設了,中間一度有發現似乎不太對勁,果然在最後整合的時候會碰到許多問題,之後應該利用這個機會在做優化時可以來仔細學習一下。

 

今日小結

今天成功將 Cannon.js 導入到專案中,目前只讓玩家本體、地面、隨機產生的箱子具有剛體效果,原本想要連苦力怕一起剛體化,但還正在 try and error 的開發階段,另外官方範例中的射擊子彈看起來也不複雜,但因為他是使用比較舊版的 Three.js,所以還需要另外把 THREE.Projector 換成 raycaster

明天順利的話應該可以將苦力怕剛體化及完成射擊子彈效果,我們明天見!

最後再附上今天的完整原始碼成果展示

 

參考資料


上一篇
用 Three.js 來當個創世神 (22):專案實作#11 - 使用 PointerLockControls 實現射擊遊戲視角 Part.2
下一篇
用 Three.js 來當個創世神 (24):專案實作#13 - 子彈射擊效果
系列文
用 Three.js 來當個創世神31

尚未有邦友留言

立即登入留言