昨天完成了計分機制的實作,今天要來將整個遊戲的流程串起來,完成進入遊戲、開始遊戲、遊戲結束、再玩一次的流程,並且實作遊戲中關於時間倒數及結束後的分數統計。
Photo by Robert Lukeman on Unsplash
這是本系列第 27 篇,如果還沒看過第 26 篇可以點以下連結前往:
用 Three.js 來當個創世神 (26):專案實作#15 - 計分機制
今天主要的工作是將整個專案的遊戲流程串起來,比較像是單純地做網頁排版與一些遊戲邏輯的撰寫,需完成以下幾項:
原本的規劃是做單頁式切換遊戲狀態,來達成整個遊戲流程,但由於要處理遊戲中 Three.js 場景重置的工比較複雜,這邊只實作了最單純的換頁模式,雖然 UX 稍微沒那麼好,但開發上快速許多。
將專案畫面拆成三頁,index.html
、game.html
、result.html
,分別代表進入遊戲頁、遊戲主頁、結束遊戲頁,並利用 Bootstrap 做簡單的樣式裝飾,完成靜態畫面的部分。
包含了標題、進入遊戲按鈕、規則說明、環境建議,標題先下一個直覺想到的「擊倒吧!苦力怕!」,目前先做簡單的排版,有時間再來優化畫面。
而特別在環境建議的地方提醒,此遊戲需使用電腦版遊玩,因為 Pointer Lock API 不支援手機版;而瀏覽器支援的部分,由於筆者是用 Google Chrome 在開發,後來實測後有一些瀏覽器某些功能會壞掉,目前暫時先不優化。
另外關於顯卡的部分,後來筆者使用手邊僅有的一台有顯卡的桌機去測試,該顯卡型號是 NVIDIA GeForce GT 730
,跑起來可以一直維持在 60 FPS
沒問題,之前一直覺得有 FPS 降低的卡頓感可能真的是筆者的 Macbook Air 的內顯不夠力了。
這邊維持之前卡 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
暫存本局遊戲的資料,並直接導頁至遊戲結束頁中計算分數。
// 從 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 表單做「排行榜」的建議,如果實作排行榜的話,或許能讓玩家因為彼此間爭奪較高排名的競爭,產生更大的誘因與黏著度,之後有時間也可以試著做做看。
今天順利將整個遊戲流程串起來了,終於比較算是個完整的遊戲,實際上玩起來有種以前在「史萊姆第一個家」玩小遊戲的感覺,專案終於也來到了尾聲,目前剩下添加音效及一些場景、光源的優化就完成了,順利的話明天就可以來做個收尾,我們明天見!
不要玩苦力怕!