iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
JavaScript

重新拿回思考力!一起來用 JavaScript 打造 CLI 好玩遊戲區!系列 第 12 篇

Day12 爆爆王4:物件顯示順序也是遊戲體驗的一環

  • 分享至 

  • xImage
  •  

今天要來撰寫炸彈爆炸的邏輯,開始之前,我們先來寫一下流程,就可以知道我們會需要經歷哪些關卡,要設計哪些函式來達成這個目標

  1. 監聽到使用者按下空白鍵
  2. 制訂一個變數來記錄炸彈的位置
  3. 印出炸彈,顯示順序要比使用者低,這樣使用者放下炸彈之後還看得到自己的位置,只有使用者離開該位置之後才看得到炸彈
  4. 三秒後主動觸發重繪爆炸
  5. 判定爆炸範圍是否有使用者存在,有的話扣一滴血
  6. 判定爆炸範圍是否有敵人存在,有的話敵人消失
  7. 判斷爆炸範圍是否有箱子存在,有的話箱子消失

由於範圍蠻大的,因此這個功能會拆成幾天來處理。

監聽使用者動作

我們目前有做到的地方:監聽使用者動作,所以我們可以從這裡著手,請在前面寫好的鍵盤監聽器 process.stdin.on('data', ...) 裡,加上空白鍵的判斷:

// 按下空白鍵放炸彈
    if (key === ' ') {
        putBomb()
    }

接著在上方加上函式:

function putBomb(){
    // 放置炸彈
}

至於函式該怎麼寫,我們先留空一下,後面的步驟再慢慢完善。

制定變數

我們在遊戲物件的區域加上炸彈,這邊會遇到一個設計抉擇:畫面上同時會有多個炸彈,還是只會有一個炸彈

也來問問自己,這兩個做法在設計上有什麼差別呢?

  1. 多個炸彈:變數就會是陣列,陣列內再裝多個炸彈的物件,因為每個炸彈的爆炸剩餘時間不一樣
  2. 單一炸彈:變數可以設計為單一物件的,因為全站只有一個炸彈,所以時間或許就不用寫進物件裡

那從遊戲經驗出發,我想應該要選擇多個炸彈的方案,所以物件的設計就會需要有xy座標再加上爆炸時間

總之先來寫看看理想的結構!先用假資料結構寫上去,之後再改。

let bombs = [{x:1,y:1}];

不過現階段還不用處理時間,所以我們先只寫這樣就好。

印出炸彈

在撰寫過程中,我決定先測試這樣的結構是否可以順利印出炸彈,然後才來依據使用者操作動態紀錄炸彈的位置。

請在 render 加上印出炸彈的邏輯:

else if (bombs.some(bomb => bomb.x === x && bomb.y === y)) {
  row += `\x1b31mB\x1b[0m`  // 用和血量值一樣的紅色和 B 來標示炸彈
}

https://ithelp.ithome.com.tw/upload/images/20260925/20182439otizFiWH9n.png

這樣就成功在 {x:1,y:1} 的位置看到炸彈了!

動態紀錄炸彈位置

在我們跑完上面的這些流程之前,或許都還會很迷惘,不確定 putBomb 的功能範圍到底會寫到哪邊,但是到這邊其實 putBomb 的功能很清楚了,就是要紀錄炸彈的座標到 bombs 裡面:

function putBomb(){
    let bombX = player.x;
    let bombY = player.y;
    bombs.push({x:bombX,y:bombY})
}

bombs 陣列要記得改回空陣列,內容由 putBomb 來處理:

let bombs = [];

測試一下

day12 放置炸彈

成功啦!我們現在可以體驗到處放炸彈了,好興奮啊!

顯示順序

不過在這時候,我想提出一個問題來給大家思考看看:你有沒有想過從推箱子遊戲到現在,為什麼 player 永遠會在最上方?為什麼當第 8 天我們遇到 player 跟 box 可以重疊的問題時,不是 box 在上面 player 在下面呢?又為什麼今天我們寫出炸彈的邏輯時,炸彈是在 player 的下面呢?

這就要談到顯示順序的設計,熟悉 CSS 的前端工程師們一定會知道 z-index 對吧?當 z-index 數值越高,就會覆蓋掉數值越低的項目,藉此來調整使用者看到畫面的層次感。

其實我們已經在不知不覺當中使用了權重的概念來撰寫物件的顯示順序,我們來看看 render 裡面這段程式碼:

if (player.x === x && player.y === y) {
  row += `\x1b[33m@\x1b[0m`
}else if (enemy1.x === x && enemy1.y === y) {
  row += `\x1b[35m&\x1b[0m`
}else if (enemy2.x === x && enemy2.y === y) {
  row += `\x1b[35m&\x1b[0m`
}else if (enemy3.x === x && enemy3.y === y) {
  row += `\x1b[35m&\x1b[0m`
}else if (boxes.some(box => box.x === x && box.y === y)) {
  row += `\x1b[34mX\x1b[0m` 
}else if (bombs.some(bomb => bomb.x === x && bomb.y === y)) {
  row += `\x1b[31mB\x1b[0m`
} else {
  row += `\x1b[32m${cell}\x1b[0m`
}

因為我們使用的是一整串 if...else if,一旦前面的條件成立,後面的 else if 就不會再繼續判斷,所以如果有偵測到 player 就會印出 player ,然後…就沒有然後了!這一格就只會印出 player ,後續就不再做判斷。

因此我們可以列出這邊的顯示順序是:player → enemy1 → enemy2 → enemy3 → boxes → bombs → 空格或牆壁(map陣列的原始值)

那這樣在我們未來的開發過程中可能會有什麼問題呢?

假設 enemy1 自動地選擇了下一步,剛好與 enemy2 重疊的話,畫面上就會少一個 enemy !又或者 player 和任何一個 enemy 重疊的時候,會顯示 player 而不是 enemy!

當然由於遊戲是動態的,或許上述的情形在只發生一瞬間的情況下都是可以接受的,因此我們可以之後遇到了再來判斷這個順序需不需要做調整。

至少我們現在可以確認 player 必須覆蓋在 bomb 之上的這個邏輯是正確的,這樣就可以囉!

那麼明天我們就要來完成三秒後爆炸並且判斷是否有玩家、敵人、箱子在爆炸範圍內的邏輯了,大家一樣可以先想想看怎麼做唷!明天見!

如果你想要瀏覽完整的程式碼,可以參考這裡:12天程式碼


上一篇
Day11 爆爆王 3:從監聽動作到儀表板印出,Terminal 環境小複習
系列文
重新拿回思考力!一起來用 JavaScript 打造 CLI 好玩遊戲區! 共 12 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言