昨天確定了遊戲內容的細節後,今天要先來實作計分機制的部分,並會套用上之前爆炸的特效,以及調整子彈威力參數與一些細節的優化。
這是本系列第 26 篇,如果還沒看過第 25 篇可以點以下連結前往:
用 Three.js 來當個創世神 (25):專案實作#14 - 遊戲內容設計
雖然昨天已經大致定好遊戲內容,但實作後仍調整了一些規則,以下簡單講解一下計分機制:
你知道嗎?向腳底下發射磚塊後快速地跳躍,能夠習得凌空微步的輕功技巧。
好像常會在遊戲中看到各種遊戲小知識的技巧,其實會不會只是工程師不想修那個 bug 呢?今天要完成以下幾項:
let creeperObj = []
// 產生十隻苦力怕
function createCreeper() {
for (let i = 0; i < 10; i++) {
creeperObj[i] = new Creeper(2, 1, i - 5) // 尺寸、重量、出生位置
scene.add(creeperObj[i].creeper)
world.addBody(creeperObj[i].headBody)
...
}
}
function render() {
requestAnimationFrame(render)
...
// 更新苦力怕畫面
for (let i = 0; i < creeperObj.length; i++) {
creeperObj[i].updateMesh() // 同步網格與剛體位置
// creeperObj[i].creeperScaleBody() // 縮放動畫
// creeperObj[i].creeperFeetWalk() // 腳走路動畫
...
}
}
因為「擊倒一隻苦力怕後隨機重生」會產生剛體與網格模型的定位問題,就是又有一些靈異現象(苦力怕重生後從天空掉下來之類的);於是後來試著將苦力怕模組改寫,能直接在初始化時宣告多隻苦力怕,發現這樣做簡單得多,至少可以確定每隻苦力怕都各自擁有一個實體,不用處理複雜的重新定位問題。
另外這邊也將原本在 render()
中一大串「網格位置與剛體同步」的邏輯改寫到 creeperModule.js
中,成為每個苦力怕物件的一個 function:
updateMesh() {
this.head.position.copy(this.headBody.position)
this.head.quaternion.copy(this.headBody.quaternion)
this.body.position.copy(this.bodyBody.position)
this.body.quaternion.copy(this.bodyBody.quaternion)
...
}
並且也試著將之前在前面做過的苦力怕動畫模組(縮放、腳擺動、追蹤鏡頭),整合成苦力怕自己的動畫方法,原本有試著要將隨機走動的動畫加進去,但跟預期中的一樣充滿了 bug,斷頭斷腳或是剛體與網格位置沒對應的老問題,總覺得因為對 Cannon.js 不夠熟悉而有許多功能暫時來不及做出來,讀者們有興趣可以再慢慢去探索了。
for (let i = 0; i < creeperObj.length; i++) {
...
// 當該隻苦力怕頭部距離地板位置小於特定距離,且第一次倒下,進行爆炸與計分
if (creeperObj[i].head.position.y < 7 && !creeperObj[i].isKnockOut) {
for (let j = 0; j < scene.children.length; j++) {
const object = scene.children[j]
// 場景內有苦力怕才爆炸
if (object.name === 'creeper') {
// 清除之前爆炸粒子
if (explosion) {
const len = explosion.length
if (len > 0) {
for (let i = 0; i < len; i++) {
explosion[i].destroy()
}
}
explosion.length = 0
}
// 移除苦力怕網格與剛體
scene.remove(creeperObj[i].creeper)
world.remove(creeperObj[i].headBody)
world.remove(creeperObj[i].bodyBody)
world.remove(creeperObj[i].leftFrontLegBody)
world.remove(creeperObj[i].leftBackLegBody)
world.remove(creeperObj[i].rightFrontLegBody)
world.remove(creeperObj[i].rightBackLegBody)
// 第一次倒地避免重複計分
creeperObj[i].isKnockOut = true
const x = creeperObj[i].body.position.x
const y = creeperObj[i].body.position.y
const z = creeperObj[i].body.position.z
// 產生爆炸
explosion[0] = new Explosion(x, y, z, 0x000000)
explosion[1] = new Explosion(x + 5, y + 5, z + 5, 0x333333)
explosion[2] = new Explosion(x - 5, y + 5, z + 10, 0x666666)
explosion[3] = new Explosion(x - 5, y + 5, z + 5, 0x999999)
explosion[4] = new Explosion(x + 5, y + 5, z - 5, 0xcccccc)
}
}
// 計分並顯示到畫面上
gameData.score += 10000
scoreDOM.textContent = gameData.score
}
}
一樣是在 index.js
中的 render()
中,去判斷每一隻苦力怕,當該隻苦力怕頭部距離地板位置小於一定值後,進行爆炸與計分。
爆炸前先將苦力怕的網格及剛體從場景中移除,特效的部分根據該隻苦力怕位置為中心進行爆炸特效。並利用 isKnockOut
旗標值確認苦力怕是第一次倒下,避免重複計分,最後則是在畫面上的分數加上 10000
分。
目前將子彈的質量設為 50
,而苦力怕的頭、身體、每一隻腳分別是 (5, 10, 10)
,所以理論上使用子彈去射擊苦力怕的身體以上的部位是可以將它擊倒的。
另外也將磚塊的質量先調低成 10
,因為之前筆者說的密技其實很容易可以發現,就是可以透過疊磚塊來撐高苦力怕來將它弄倒,但把疊磚的功能移除又沒辦法靠著蓋磚塊往上爬,只好繼續保留了,並且將參數調整成「用疊磚擊倒不會比較快」的狀況,實測過幾次後發現,因為目前苦力怕站得比較密集,常常會因為倒在其他人身上而形成支撐,所以用磚塊撐高不見得會比較快,應該會需要多一些技巧吧!
if (ammos.length > 50) {
for (let i = 0; i < ammos.length; i++) {
ammoMeshes[i].geometry.dispose()
scene.remove(ammoMeshes[i])
world.remove(ammos[i])
}
ammos.length = 0
ammoMeshes.length = 0
}
前天有提到,過多的子彈會造成 FPS 狂降,加上現在場景內的東西越加越多,還是決定做一下子彈的優化,當子彈數量超過 50
顆時,將用不到的子彈都釋放掉,改善效能。
FPS 下降問題似乎還是存在,不確定是不是因為筆者是使用顯卡不太夠力的 Macbook Air 的關係,如果加上雪景後甚至會在進入遊戲時就降到 20 ~ 30
,在要求技巧的射擊遊戲中 lag 真的會是一個致命傷呀,算是一個待優化的問題。
後來使用有顯卡(GeForce GT 730)的桌機測試有雪景的版本,不管磚塊再多都能維持在 60 FPS 上下,超級順,看來內顯真的是齁不住...
今天完成了遊戲內容中的計分機制,也成功地套用上之前實作出的爆炸特效,明天會來實作整個遊戲流程的串接,包括時間結束畫面及分數統計等等,我們明天見!