iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 28
3
Modern Web

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

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

昨天完成了計分機制的實作,今天要來將整個遊戲的流程串起來,完成進入遊戲、開始遊戲、遊戲結束、再玩一次的流程,並且實作遊戲中關於時間倒數及結束後的分數統計。

27_front

Photo by Robert Lukeman on Unsplash


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

用 Three.js 來當個創世神 (26):專案實作#15 - 計分機制


專案實作

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

今天主要的工作是將整個專案的遊戲流程串起來,比較像是單純地做網頁排版與一些遊戲邏輯的撰寫,需完成以下幾項:

  • 遊戲進入頁
  • 遊戲開始前導頁
  • 遊戲中時間倒數功能
  • 遊戲結束頁與分數統計

原本的規劃是做單頁式切換遊戲狀態,來達成整個遊戲流程,但由於要處理遊戲中 Three.js 場景重置的工比較複雜,這邊只實作了最單純的換頁模式,雖然 UX 稍微沒那麼好,但開發上快速許多。

將專案畫面拆成三頁,index.htmlgame.htmlresult.html,分別代表進入遊戲頁、遊戲主頁、結束遊戲頁,並利用 Bootstrap 做簡單的樣式裝飾,完成靜態畫面的部分。

 

遊戲進入頁

27_demo

包含了標題、進入遊戲按鈕、規則說明、環境建議,標題先下一個直覺想到的「擊倒吧!苦力怕!」,目前先做簡單的排版,有時間再來優化畫面。

而特別在環境建議的地方提醒,此遊戲需使用電腦版遊玩,因為 Pointer Lock API 不支援手機版;而瀏覽器支援的部分,由於筆者是用 Google Chrome 在開發,後來實測後有一些瀏覽器某些功能會壞掉,目前暫時先不優化。

另外關於顯卡的部分,後來筆者使用手邊僅有的一台有顯卡的桌機去測試,該顯卡型號是 NVIDIA GeForce GT 730,跑起來可以一直維持在 60 FPS 沒問題,之前一直覺得有 FPS 降低的卡頓感可能真的是筆者的 Macbook Air 的內顯不夠力了。

 

遊戲開始前導頁

27_demo2

這邊維持之前卡 PonintLockControls 的遮罩,並利用「開始遊戲」按鈕引導使用者點擊中央開始遊戲,可以順便決定射擊準心,另外也貼心地放上「重新開始」的按鈕,如果本局不幸沒有操作好,可以馬上重新遊戲。

 

遊戲中時間倒數功能

時間倒數功能實作在 game.js 中的 render()

if (parseInt(gameData.remainingTime / 1000) > 0) {
  gameData.remainingTime -= new Date() - gameData.prevTime
  remainingTimeDOM.textContent = parseInt(gameData.remainingTime / 1000)
  gameData.prevTime = new Date()
} else {
  handleEndGame()
}

一開始先設定 60 秒的剩餘時間,隨著每一次重新渲染讓剩餘時間遞減。特別要注意的是,在 pointerLockControlsModule.js 中使用 Esc 暫停遊戲時,需要在「繼續遊戲」時,使用當下的時間為 gameData.prevTime,才不會有暫停遊戲時,時間仍在繼續倒數的 bug。

 

處理遊戲結束條件

function handleEndGame() {
  localStorage.setItem('NEW_GAME_RESULT', JSON.stringify(gameData))
  window.location.replace('./result.html')
}

遊戲的結束條件有兩個,一個是時間用完,另一個是十隻苦力怕都被擊倒,而這邊在 game.js 中的 handleEndGame() 來處理遊戲結束的工作。這裡利用 localStorage 暫存本局遊戲的資料,並直接導頁至遊戲結束頁中計算分數。

 

遊戲結束頁統計分數

27_demo3

// 從 localStorage 中讀取本局遊戲資料及玩家的最佳分數資料
newGameResult = JSON.parse(localStorage.getItem('NEW_GAME_RESULT'))
bestGameResult = localStorage.getItem('BEST_GAME_RESULT')
  ? localStorage.getItem('BEST_GAME_RESULT')
  : 0

let newScore = 0

const newScoreDOM = document.getElementById('newScore')
const bestScoreDOM = document.getElementById('bestScore')

// 統計本局分數,為擊倒得分加上剩餘時間的加成分數
newScore =
  newGameResult.score + parseInt(newGameResult.remainingTime / 1000) * 1000

newScoreDOM.textContent = newScore

// 本局分數超越歷史新高,將高分寫進 BEST_GAME_RESULT 的 localStorage 中
if (newScore > parseInt(bestGameResult)) {
  localStorage.setItem('BEST_GAME_RESULT', newScore)
  bestGameResult = newScore
}

bestScoreDOM.textContent = bestGameResult

結束頁的部分利用 localStorage 作為本地端暫存資料,讓兩個頁面間可以傳遞遊戲分數資料,並設定個人的最高分做為一個讓玩家想挑戰自己的誘因,在遊戲中持續破紀錄。

這邊 Gary 大大有提供筆者一個使用 Google 表單做「排行榜」的建議,如果實作排行榜的話,或許能讓玩家因為彼此間爭奪較高排名的競爭,產生更大的誘因與黏著度,之後有時間也可以試著做做看。

 

今日小結

今天順利將整個遊戲流程串起來了,終於比較算是個完整的遊戲,實際上玩起來有種以前在「史萊姆第一個家」玩小遊戲的感覺,專案終於也來到了尾聲,目前剩下添加音效及一些場景、光源的優化就完成了,順利的話明天就可以來做個收尾,我們明天見!

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

27_demo4

不要玩苦力怕!


上一篇
用 Three.js 來當個創世神 (26):專案實作#15 - 計分機制
下一篇
用 Three.js 來當個創世神 (28):專案實作#17 - 遊戲完成!
系列文
用 Three.js 來當個創世神31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言