iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 29
0

今天是專案的最後一個 Part 啦!要來加入背景音效、爆炸音效、射擊音效,並做場景中的草地貼圖、磚塊貼圖與光源調整,另外最後會試著練習載入外部模型做個完整的收尾。

28_front

Photo by Jason Leung on Unsplash


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

用 Three.js 來當個創世神 (27):專案實作#16 - 遊戲流程、時間倒數、分數統計


今日目標

28_demo

請參考完整原始碼成果展示

今天是專案實作的最後一天,將一路走來的苦力怕遊戲做個最後收尾,要做的有:

  • 遊戲音效
  • 載入外部模型
  • 場景優化

 

1. 遊戲音效

音效可以說是遊戲中不可或缺的要素之一,甚至可以說是靈魂也不為過,加上了音效之後,整個遊戲似乎都活了過來。而在這個專案中筆者加入了其中三個必要的音效:

  • 背景音樂
  • 爆炸音效
  • 射擊音效

以下分別說明實作的部分。

背景音樂

筆者到 Youtube 聽了許多的免版權音樂後,最後選擇了一首比較史詩風格的背景音樂「Ansia Orchestra – Mercury」,程式碼的部分很簡單,就是在 game.html 加入 audio tag:

<audio id="bgm" src="./music/bgm.mp3"></audio>

並且在 game.js 中設定音量與在 pointerLockControlsModule.js 設定暫停及播放時機:

// game.js
const bgm = document.getElementById('bgm')
bgm.volume = 0.5 // 音量 50%
// pointerLockControlsModule.js
if (
  document.pointerLockElement === element ||
  document.mozPointerLockElement === element ||
  document.webkitPointerLockElement === element
) {
  ...
  bgm.play()
} else {
  bgm.pause()
}

爆炸音效

爆炸音效使用在 Minecraft 原作中同樣的音效,而實作方法與上面差不多,只是播放時機是在每一隻苦力怕倒地爆炸時觸發。

射擊音效

原本筆者找了一些免費音效檔並試著自己裁減音效,發現射擊音效要直接使用外部音檔播放長度還不可以過長,不然當射擊速度較快時,就會有幾顆子彈沒有聲音。

後來隔壁棚寫 Web Audio 鐵人的 Gary 大大表示:「射擊音效用 Tone.js 的合成器做很簡單啊!」於是他就速度地幫忙提供了一個模擬射擊的音效,感謝支援:

function initGunShotSound() {
  const filter = new Tone.Filter(1800, 'lowpass').toMaster()
  synth = new Tone.NoiseSynth({
    noise: {
      type: 'white',
      playbackRate: 2
    },
    envelope: {
      attack: 0.005,
      decay: 0.1,
      sustain: 0.0001,
      release: 0.1
    }
  }).connect(filter)
}

// shooting event
window.addEventListener('click', function(e) {
  if (controls.enabled == true) {
    if (e.which === 1) {
      // 射擊聲
      synth.triggerAttackRelease('0.01') // 音效持續秒數
      ...
    }
    ...
  }
}

詳細的 Tone.js 介紹,在 Gary 大大的鐵人賽文章講解得非常詳細,對 Web Audio API 有興趣的讀者也可以參考他的系列文

 

2. 載入外部模型

28_obj

由於是專案最後一天了,一直想來試試載入外部模型的實作,於是到 Free3D 上面找了一個木製守望台冰鎮 Corona.obj 檔模型載入到場景中玩玩。

// 載入守望塔模型
function createTower() {
  let towerBumpMat = new THREE.MeshStandardMaterial({
    metalness: 0.05,
    roughness: 0.9
  })
  towerBumpMat.map = textureLoader.load(
    './obj/tower/textures/Wood_Tower_Col.jpg'
  )
  loader.load('./obj/tower/tower.obj', function(loadedMesh) {
    loadedMesh.children.forEach(function(child) {
      child.material = towerBumpMat
      child.geometry.computeFaceNormals()
      child.geometry.computeVertexNormals()
    })
    loadedMesh.scale.set(10, 10, 10)
    loadedMesh.position.set(0, -8, 100)
    loadedMesh.castShadow = true
    scene.add(loadedMesh)
  })
}

// 載入可樂娜模型
function createCorona() {
  let coronaBumpMat = new THREE.MeshStandardMaterial({
    metalness: 0.05,
    roughness: 0.9
  })
  coronaBumpMat.map = textureLoader.load('./obj/Corona/BotellaText.jpg')
  coronaBumpMat.bumpMap = textureLoader.load('./obj/Corona/BotellaText.jpg')
  coronaBumpMat.bumpScale = 1
  loader.load('./obj/Corona/Corona.obj', function(loadedMesh) {
    loadedMesh.children.forEach(function(child) {
      child.material = coronaBumpMat
      child.geometry.computeFaceNormals()
      child.geometry.computeVertexNormals()
    })
    // loadedMesh.scale.set(0.2, 0.2, 0.2)
    loadedMesh.position.set(30, 0, 100)
    loadedMesh.rotation.x = -0.3
    loadedMesh.rotation.y = 0.5
    loadedMesh.rotation.z = -0.5
    loadedMesh.castShadow = true
    scene.add(loadedMesh)
  })
}

在 Three.js 中可以載入許多種檔案類型的外部模型,這應該也是一般開發中常用的建模方法,而筆者在這邊只實驗 .obj 檔的載入,需要引入額外的函式庫 OBJLoader.js官方下載網址)即可使用。

比較可惜的是,這個只是網格模型,沒有剛體效果,如果這個守望塔能做成剛體效果一定很好玩,稍微查了一下查到一篇 Cannon.js 作者在 Stackoverflow 上回答的文章,作者表示可以透過他寫的 Trimesh 類別來做碰撞偵測,但僅止支援球體與平面的碰撞。

而一般比較有效率的做法會將模型載到 Unity、PlayCanvas 等內建支援 WebGL 物理引擎的軟體做物理效果。

 

3. 場景優化

草地貼圖

場景優化的部分,參考官方衣服飄動的範例將地板貼上了草皮貼圖:

const groundTexture = textureLoader.load('./img/grasslight-big.jpg')
groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping
groundTexture.repeat.set(25, 25)
groundTexture.anisotropy = 16

const groundMaterial = new THREE.MeshLambertMaterial({ map: groundTexture })

磚塊凹凸貼圖

右鍵磚塊的部分,參考 K 大大當初的教學將磚塊補上了凹凸貼圖的效果:

28_bumpmap

let brickBumpMat = new THREE.MeshStandardMaterial({
  metalness: 0.1,
  roughness: 0.8
})
brickBumpMat.map = textureLoader.load('./img/brickNormal.jpg')
brickBumpMat.bumpMap = textureLoader.load('./img/brickBumpMap.jpg')
brickBumpMat.bumpScale = 1
const brickMesh = new THREE.Mesh(boxGeometry, brickBumpMat)

不過因為圖片載入似乎會有稍微的延遲,在快速點右鍵時,看起來有一點閃爍不太舒服,有點考慮是不是要優化或拿掉。

光源調整

光源的部分加上了平行光及半球光補強:

let directionalLight = new THREE.DirectionalLight(0x555555)
directionalLight.position.set(100, 100, 100)
scene.add(directionalLight)

let hemiLight = new THREE.HemisphereLight(0x000022, 0x002200, 0.5)
hemiLight.position.set(0, 300, 0)
scene.add(hemiLight)

另外也順手讓苦力怕站的分開一點,將時間上調至四分鐘(因為 BGM 大約四分鐘懶得剪 XD),場地加大,讓喜歡散步的玩家可以有多一點活動的空間。

 

專案後記

專案也終於在上面的補強後告一個段落,而可以優化的地方仍然相當地多,像是走路音效、排行榜、時間倒數提醒、遊戲結束的提示與過場載入畫面、I18N、一頁式遊戲流程、苦力怕在所屬領地內隨機走動、苦力怕攻擊玩家等等,還有太多的細節與實作可以發揮,但遊戲的第一版,就用一瓶 Corona 暫時畫下句點吧。

想當初在「專案實作#1」時還沒什麼信心可以完成遊戲專案,那時才掌握了基本的建模與動畫,對粒子系統與物理引擎都不熟悉的情況下,可以一路上跌跌撞撞,最後完成作品還是蠻感動的。

只能說親自體驗到,做遊戲真的不是一件容易的事,尤其是一人獨力完成開發,又是時間有限的情況下,只能不斷的取捨功能要不要做、 bug 要不要解,下次有機會再做遊戲的話,應該組個隊來開發才是。

 

今日小結

今天將專案做了個大收尾,加上了遊戲音效,並且將場景的貼圖及光源做了優化,並載入幾個外部模型到場景中做裝飾,順利在鐵人賽的最後幾天內,將整個苦力怕射擊遊戲專案完成,明天會來做整個系列文的總結,並分享相關的延伸學習資源,我們明天見!

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

 

參考資料


上一篇
用 Three.js 來當個創世神 (27):專案實作#16 - 遊戲流程、時間倒數、分數統計
下一篇
用 Three.js 來當個創世神 (29):大綱與 Three.js 學習資源
系列文
用 Three.js 來當個創世神31

1 則留言

1
Gary
iT邦新手 5 級 ‧ 2018-11-14 09:43:07

太強啦~~~
難度好高,打到 15 隻就緊繃了 XD

Dez iT邦新手 5 級‧ 2018-11-14 16:59:16 檢舉

我自己實測後,最高只打了 16 隻,難度看起來剛好,蠻有挑戰性的/images/emoticon/emoticon01.gif

我要留言

立即登入留言