發(fā)實(shí)戰(zhàn):從地圖構(gòu)建到碰撞檢測(cè))
如果你想在 Game Boy 上跑一款自己寫(xiě)的游戲《黑城堡 2》這類(lèi)項(xiàng)目是最適合拆開(kāi)看的實(shí)踐案例。它的價(jià)值不在于畫(huà)面有多炫而在于把一臺(tái)只跑 8 位代碼、屏幕只有 160x144、總內(nèi)存以 KB 計(jì)算的掌機(jī)當(dāng)成目標(biāo)平臺(tái)逼你把玩法、資源、代碼和發(fā)布路徑全部壓縮到最小閉環(huán)里。如果你正在學(xué)游戲開(kāi)發(fā)想脫離 PC 網(wǎng)頁(yè)和手機(jī) App 的舒適區(qū)或者純粹想做一個(gè)能放進(jìn)模擬器甚至燒錄到實(shí)機(jī)卡帶里的作品這篇文章會(huì)告訴你一條真實(shí)可走的路。我不打算把《黑城堡 2》包裝成什么神作也不會(huì)給你畫(huà)一張“從此靠在復(fù)古游戲圈出名”的大餅。我只按自己制作自制 Game Boy 游戲的實(shí)際流程把項(xiàng)目定位、工具鏈、最小可玩原型、資源限制、編譯測(cè)試和排錯(cuò)思路完整講一遍。你看完至少要能回答三個(gè)問(wèn)題我的游戲在 GB 上能不能跑地圖和角色應(yīng)該怎么拆踩坑時(shí)先查哪里1. 先給《黑城堡 2》定一個(gè)能落地的范圍1.1 它到底是動(dòng)作游戲還是解謎游戲沒(méi)有范圍的項(xiàng)目十個(gè)有九個(gè)死在“想做的事太多”。自制 Game Boy 游戲尤其如此。GB 的機(jī)能擺在那里CPU 主頻約 4.19 MHz畫(huà)面分辨率 160x144一屏能同時(shí)顯示的對(duì)象和 tile 都有限。你不可能在這里做無(wú)縫大地圖加實(shí)時(shí)物理引擎加復(fù)雜粒子特效?!逗诔潜?2》這個(gè)標(biāo)題天然帶兩件事一是“黑城堡”說(shuō)明場(chǎng)景要圍繞城堡展開(kāi)二是“2”說(shuō)明它應(yīng)該有前作留下的世界觀(guān)或機(jī)制延續(xù)。但作為自制游戲最合理的落地方式偏動(dòng)作解謎玩家控制一個(gè)小勇士在城堡房間之間穿行擊敗敵人、找到鑰匙、打開(kāi)門(mén)、觸發(fā)機(jī)關(guān)、最終進(jìn)入 BOSS 房間。我建議把“解謎”部分控制在開(kāi)關(guān)門(mén)、移動(dòng)石塊、按順序激活機(jī)關(guān)這個(gè)級(jí)別。不要想金幣、裝備、技能樹(shù)、多武器切換、對(duì)話(huà)分支這些內(nèi)容那是另一款游戲的體量。1.2 為什么先做單屏房間而不是長(zhǎng)卷軸這是很多新手最容易搞錯(cuò)的地方。不少人一上來(lái)就想做“全城堡連續(xù)滾動(dòng)地圖”結(jié)果被地圖拼接、鏡頭邊界、敵人刷新和碰撞回退折磨到棄坑。GB 的性能約束下連續(xù)卷軸不是不行但需要處理的地圖分塊邏輯會(huì)成倍增加。更穩(wěn)的起點(diǎn)是“房間制”。每個(gè)房間固定 160x144畫(huà)面一次只顯示一個(gè)區(qū)域。角色走到房間邊緣的門(mén)或樓梯時(shí)切換整個(gè)房間數(shù)據(jù)。這樣做有三個(gè)好處地圖數(shù)據(jù)可以按房間獨(dú)立存儲(chǔ)單個(gè)房間尺寸小省 ROM 也省內(nèi)存。碰撞判斷只需要處理當(dāng)前房間不會(huì)遇到“地圖邊緣 vs 鏡頭邊緣”的復(fù)雜交叉。調(diào)試時(shí)一眼就能定位問(wèn)題要么畫(huà)面切了但數(shù)據(jù)沒(méi)更新要么數(shù)據(jù)更新了但角色坐標(biāo)沒(méi)重設(shè)鏈路比連續(xù)卷軸短得多。對(duì)《黑城堡 2》來(lái)說(shuō)我甚至建議早期版本只做 3 個(gè)房間一個(gè)入口大廳、一個(gè)敵人房間、一個(gè) BOSS 房間。能把這 3 個(gè)房間跑通整個(gè)項(xiàng)目的骨架就立住了。之后再往里面加房。2. 準(zhǔn)備工具鏈GB 自制項(xiàng)目的三類(lèi)必備件2.1 ROM 編譯工具GBDK、RGBDS、GB Studio 怎么選做 Game Boy 自制游戲工具鏈選型基本決定了你的開(kāi)發(fā)體驗(yàn)。主流的三種選擇是 GBDK、RGBDS 和 GB Studio它們的定位區(qū)別很大。GBDK 是目前最常用的 C 開(kāi)發(fā)工具鏈。它允許你用類(lèi) C 語(yǔ)言寫(xiě)邏輯然后編譯成 GB ROM。好處是對(duì)不熟悉匯編的開(kāi)發(fā)者友好開(kāi)發(fā)效率高壞處是 C 抽象會(huì)帶來(lái)一些額外開(kāi)銷(xiāo)而且碰到底層問(wèn)題時(shí)光看 C 不容易定位。如果你只想用一個(gè)月做到可玩原型GBDK 是性?xún)r(jià)比最高的選擇。注意不要默認(rèn)自己電腦上已經(jīng)裝好安裝時(shí)先確認(rèn)當(dāng)前版本的依賴(lài)要求不同分支對(duì)主機(jī)系統(tǒng)有差異。RGBDS 是匯編語(yǔ)言工具鏈適合想徹底掌控每一個(gè)字節(jié)的人。它的優(yōu)點(diǎn)是可玩性上限高能精細(xì)控制內(nèi)存和時(shí)間缺點(diǎn)也一樣明顯寫(xiě) 500 行匯編可能只為處理一個(gè)菜單。除非你的目標(biāo)就是練匯編或挑戰(zhàn)極限否則我第一次做《黑城堡 2》時(shí)會(huì)繞過(guò)它。GB Studio 是可視化開(kāi)發(fā)工具類(lèi)似 RPG Maker 的思路。你不寫(xiě)代碼而是通過(guò)界面放置事件、場(chǎng)景和對(duì)話(huà)。它適合做劇情驅(qū)動(dòng)的簡(jiǎn)單 RPG但對(duì)動(dòng)作戰(zhàn)斗、復(fù)雜碰撞、自定義敵人 AI 這些需求會(huì)越做越別扭。如果你只是想做“一個(gè)城堡里跑來(lái)跑去開(kāi)門(mén)開(kāi)寶箱的演示”GB Studio 很輕松但你想做“有攻擊判定、有敵人巡邏、有狀態(tài)切換的動(dòng)作解謎”GBDK 更合適。我自己的經(jīng)驗(yàn)是把《黑城堡 2》當(dāng)成學(xué)習(xí)項(xiàng)目時(shí)直接用 GBDK編譯命令簡(jiǎn)單循環(huán)和數(shù)據(jù)結(jié)構(gòu)的寫(xiě)法和 C 幾乎一樣社區(qū)示例也多。等你能熟練處理地圖切換和碰撞了再?zèng)Q定要不要碰匯編。2.2 測(cè)試工具模擬器、真機(jī)燒錄卡、調(diào)試輸出和只做網(wǎng)頁(yè)游戲不同Game Boy 自制項(xiàng)目不能“刷新瀏覽器”就完事。你至少需要三樣?xùn)|西模擬器。這是你 90% 時(shí)間使用的測(cè)試環(huán)境。選模擬器時(shí)不要只看能不能跑要看它有沒(méi)有 tile viewer、內(nèi)存查看器、斷點(diǎn)調(diào)試和幀步進(jìn)功能。你在開(kāi)發(fā)中遇到的“畫(huà)面花屏”“按鍵延遲”“精靈閃爍”很多都要靠這些工具才能看清。燒錄卡 / flash cart。它能把編譯出來(lái)的 ROM 文件寫(xiě)到實(shí)體卡帶里插入真機(jī)運(yùn)行。自制游戲領(lǐng)域常見(jiàn)的做法是買(mǎi)一塊可復(fù)寫(xiě)燒錄卡價(jià)格不一但功能邏輯都類(lèi)似連接電腦、寫(xiě)入 ROM、插入 GB 或兼容掌機(jī)。調(diào)試日志。GB 上沒(méi)有標(biāo)準(zhǔn) stdout但你可以用模擬器的調(diào)試窗口輸出信息或者把調(diào)試信息編碼成屏幕上的特定顏色塊。我的習(xí)慣是先把角色坐標(biāo)、當(dāng)前房間號(hào)、當(dāng)前幀動(dòng)作編號(hào)顯示在畫(huà)面上方一行不顯眼的位置這樣卡住時(shí)一眼就能讀到狀態(tài)。不要一上來(lái)就買(mǎi)設(shè)備。先把模擬器配合 GBDK 的示例工程跑起來(lái)確認(rèn)工具鏈可用再?zèng)Q定要不要上實(shí)機(jī)。實(shí)機(jī)測(cè)試是發(fā)布前的最后一道門(mén)不是開(kāi)發(fā)初期的必需品。3. 從地圖塊和角色碰撞開(kāi)始最小可玩原型怎么做3.1 地圖和碰撞從 8x8 到整屏 160x144Game Boy 的畫(huà)面基礎(chǔ)單位是 8x8 像素的 tile。一個(gè) 160x144 的屏幕橫向 20 個(gè) tile豎向 18 個(gè) tile總共 360 個(gè) tile。背景地圖就是由這些 tile 編號(hào)拼接出來(lái)的二維數(shù)組。做《黑城堡 2》的時(shí)候我建議先把地圖抽象成兩層一層是視覺(jué)層負(fù)責(zé)顯示什么圖形另一層是碰撞層負(fù)責(zé)判斷能不能走。視覺(jué)層用普通 tile 編號(hào)碰撞層用一個(gè) 20x18 的數(shù)組每個(gè)元素表示“空地、墻、門(mén)、巖漿、鑰匙點(diǎn)”等狀態(tài)。角色移動(dòng)時(shí)不要拿角色貼圖去和視覺(jué)層撞也不要想當(dāng)然地從“地圖圖片顏色”判斷而是直接讀取碰撞層數(shù)組里對(duì)應(yīng)格子的值。為什么這么做因?yàn)樵?GB 上地圖磚塊和碰撞邏輯必須解耦。你可能想要一個(gè)看起來(lái)像柱子但實(shí)際可以穿過(guò)的裝飾或者看起來(lái)像平地但走過(guò)去會(huì)觸發(fā)陷阱的房間。如果碰撞和顯示綁在一起做這些細(xì)節(jié)時(shí)會(huì)非常痛苦。地圖數(shù)據(jù)要盡量壓縮。20x18 的二維數(shù)組直接存一個(gè)房間 360 字節(jié)看起來(lái)不多但幾十個(gè)房間加多方向翻版文件就會(huì)膨脹。常見(jiàn)的做法是把連續(xù)相同 tile 的橫排做行程壓縮或者直接用手寫(xiě)地圖編輯器導(dǎo)成壓縮格式。這一步等房間多了之后一定要做早期幾個(gè)房間不必糾結(jié)。3.2 角色移動(dòng)和按鍵映射GB 有方向鍵、A、B、Start、Select 一共 8 個(gè)主要輸入。自制游戲里按鍵映射建議遵循直覺(jué)方向鍵控制移動(dòng)。A 鍵負(fù)責(zé)上下文交互開(kāi)門(mén)、推動(dòng)石塊、檢查寶箱。B 鍵負(fù)責(zé)攻擊或跳躍二選一。Start 鍵打開(kāi)暫停菜單。Select 鍵預(yù)留可以用作切換物品或地圖信息。移動(dòng)邏輯不要做成“按下方向鍵直接改變角色像素坐標(biāo)”而是“先判斷是否滿(mǎn)足移動(dòng)條件再更新坐標(biāo)”。對(duì)于房間制游戲我建議按 tile 鎖定移動(dòng)角色位置不是 0 到 159 的任意像素而是某個(gè) tile 格內(nèi)的偏移。這樣碰撞判斷簡(jiǎn)單動(dòng)畫(huà)也更穩(wěn)定。如果你想讓操控更順滑可以做半格偏移但第一版不必。處理順序很關(guān)鍵。每一幀的角色更新應(yīng)該是讀取輸入 - 計(jì)算目標(biāo)位置 - 檢測(cè)目標(biāo)位置的碰撞層 - 如果可通行則更新坐標(biāo) - 檢測(cè)房間邊緣的門(mén)事件 - 繪制角色。不要倒過(guò)來(lái)先畫(huà)再判斷碰撞會(huì)導(dǎo)致角色卡進(jìn)墻里一幀再被彈出來(lái)視覺(jué)上就是抖動(dòng)。3.3 一個(gè)最小可玩幀的邏輯順序?qū)懘a前先畫(huà)一下單幀邏輯。下面是一個(gè)只針對(duì)房間制動(dòng)作解謎的偽代碼示例具體語(yǔ)法取決于你選的語(yǔ)言while (game_running) { read_input(); if (player_attack_pressed) { spawn_attack_hitbox(); play_attack_sound(); } target_x player.x player.velocity_x; target_y player.y player.velocity_y; if (is_walkable(target_x, target_y)) { player.x target_x; player.y target_y; } check_door_trigger(player.x, player.y); check_item_pickup(player.x, player.y); update_enemies(); update_bullets(); draw_background_tiles(); draw_objects(); draw_player_sprite(); wait_for_vblank(); }這里真正的難點(diǎn)不是循環(huán)本身而是 update_enemies 和 update_bullets 的狀態(tài)切換。敵人巡邏、敵人受傷、敵人死亡、玩家攻擊、玩家無(wú)敵幀這些狀態(tài)如果混在一起很快就會(huì)出現(xiàn)“角色明明站在墻邊卻被敵人打飛”的奇怪 bug。我的建議是給敵人和玩家都單獨(dú)設(shè)計(jì)狀態(tài)變量例如玩家有 idle、move、attack、hurt、dead 五種狀態(tài)每幀必須先處理狀態(tài)切換再計(jì)算結(jié)果。一個(gè)小技巧所有會(huì)移動(dòng)的對(duì)象包括玩家、敵人、子彈都要有一個(gè)“速度”字段。不要直接寫(xiě)“角色每幀移動(dòng) 3 像素”因?yàn)椴煌瑺顟B(tài)動(dòng)畫(huà)幀率不同直接寫(xiě)死容易出現(xiàn)“時(shí)快時(shí)慢”。速度字段配合統(tǒng)一的計(jì)時(shí)器來(lái)更新后續(xù)調(diào)試會(huì)輕松很多。4. 精靈、背景和音效在 160x144 畫(huà)面上做取舍4.1 四個(gè)灰階和雙調(diào)色板Game Boy 沒(méi)有彩色屏幕顯示的是四種灰階從最暗到最亮大約對(duì)應(yīng) 0、1、2、3 四級(jí)。這看起來(lái)是很硬的限制但它也帶來(lái)了一個(gè)好處你不必糾結(jié)色相只需要關(guān)注明度層次和可讀性。實(shí)際做《黑城堡 2》的畫(huà)面時(shí)我會(huì)把背景和精靈分開(kāi)處理。GB 上有獨(dú)立的背景調(diào)色板和精靈調(diào)色板每個(gè)調(diào)色板可以定義四色。也就是說(shuō)背景畫(huà)面最多同時(shí)出現(xiàn)灰階 0 到 3精靈也最多同時(shí)出現(xiàn)灰階 0 到 3但兩套調(diào)色板可以不同配置。你要關(guān)心的第一件事不是美術(shù)風(fēng)格而是陣營(yíng)辨識(shí)度。城堡墻壁用中暗色地面用中亮色玩家角色精靈用“幾乎全黑 最亮高光”敵人用“中灰”層次。這樣玩家在低分辨率的畫(huà)面上也能一眼區(qū)分“我能走的”“我不能走的”“我是誰(shuí)”“誰(shuí)會(huì)打我”。四個(gè)灰階聽(tīng)起來(lái)簡(jiǎn)單實(shí)際規(guī)劃時(shí)依然會(huì)翻車(chē)。最常見(jiàn)的問(wèn)題是把背景調(diào)色板里所有顏色都排在灰階 1 和 2導(dǎo)致地圖看起來(lái)灰成一片。最好在開(kāi)發(fā)早期就固定一套“灰到黑的場(chǎng)景層次表”邊緣墻最深地面中等可交互物體最亮背景裝飾只占 1 個(gè)灰階。4.2 tile 和 sprite 的限制GB 的一個(gè)精靈sprite默認(rèn)是 8x8 或 8x16 像素。一個(gè)屏幕上同時(shí)能顯示的精靈數(shù)量有限而且同一行內(nèi)也有嚴(yán)格限制。超限時(shí)你會(huì)看到角色突然消失半截或敵人閃爍。這不是隨機(jī) bug而是硬件限制。所以《黑城堡 2》里我并不會(huì)讓每個(gè)敵人都是一個(gè)獨(dú)立 sprite 就完事。相反我會(huì)提前統(tǒng)計(jì)一屏里最多可能出現(xiàn)多少個(gè)“需要獨(dú)立活動(dòng)”的對(duì)象玩家 1 個(gè)敵人最多 4 個(gè)飛來(lái)的子彈 2 個(gè)掉落的鑰匙 1 個(gè)受擊特效 1 個(gè)。如果遠(yuǎn)低于硬件上限那我可以放心把特效做成獨(dú)立精靈如果已經(jīng)接近或超出就要考慮把某些特效直接畫(huà)在背景 tile 上或者用“共享精靈”的方式復(fù)用。背景層面的 tile 也是稀缺資源。GB 的顯存里能同時(shí)保存的 tile 圖案數(shù)量有限你不能讓每個(gè)房間的所有圖形都完美無(wú)缺地一次性加載。通用做法是分房間加載進(jìn)入新房間時(shí)只把該房間用到的 tile 寫(xiě)入顯存退出后切換到下一組。最開(kāi)始做 3 個(gè)房間的規(guī)模時(shí)還能做成全量加載房間數(shù)一多就必須做分房 tile 管理。判斷標(biāo)準(zhǔn)很簡(jiǎn)單如果某個(gè)房間的 tile 數(shù)超過(guò)了顯存容量畫(huà)面會(huì)隨機(jī)出現(xiàn)亂碼或重復(fù)圖案這時(shí)你要做的不是加大顯存而是精簡(jiǎn) tile 或拆分房間。4.3 音效與音樂(lè)兩條路都別指望復(fù)雜混音Game Boy 的聲音是方波、三角波、噪聲等合成聲沒(méi)有采樣播放能力。你不可能在 GB 上直接丟入一段 MP3必須通過(guò) tracker 軟件寫(xiě)音樂(lè)數(shù)據(jù)再轉(zhuǎn)換成 ROM 里的音效序列數(shù)據(jù)?!逗诔潜?2》這種動(dòng)作游戲音效至少需要覆蓋幾個(gè)場(chǎng)景開(kāi)關(guān)鍵音、門(mén)打開(kāi)音、寶箱開(kāi)啟音、怪物被打音、玩家受傷音。我的建議是先從簡(jiǎn)單的噪聲音和短方波音做起不要追求完整 BGM。短音效很容易做出手感而一首完整的背景音樂(lè)在沒(méi)有經(jīng)驗(yàn)的情況下極容易變成“聽(tīng)不見(jiàn)的噪音背景”。你可以考慮用類(lèi)似 tracker 思路的工具來(lái)排音軌但第一版我只推薦給玩家一個(gè)極簡(jiǎn)循環(huán)主旋律加一兩條音效。如果一個(gè)音效的播放會(huì)讓角色移動(dòng)明顯掉幀趕緊降低音效播放頻率或者在攻擊動(dòng)畫(huà)中延遲一拍再播放。聲音在 GB 上不只是娛樂(lè)它會(huì)影響玩家對(duì)動(dòng)作判定的感知所以“打擊反饋音”比環(huán)境氛圍音重要得多。5. 編譯、模擬器測(cè)試和實(shí)機(jī)燒錄驗(yàn)證5.1 編譯流程和 ROM 大小檢查用 GBDK 或 RGBDS 構(gòu)建 ROM本質(zhì)上就是把源代碼和資源文件編譯打包成一個(gè) .gb 文件。這個(gè)流程不要當(dāng)作“最后一步”而是在寫(xiě)完第一段代碼時(shí)就建立起來(lái)。我的做法是維護(hù)一個(gè) Makefile 或構(gòu)建腳本至少包含編譯 C 源碼為目標(biāo)文件。將圖片轉(zhuǎn)換工具導(dǎo)出的 tile 和地圖數(shù)據(jù)鏈接進(jìn)來(lái)。把音效數(shù)據(jù)寫(xiě)入 ROM。輸出最終 .gb 文件。自動(dòng)輸出編譯警告和 ROM 大小。ROM 大小是必須盯的指標(biāo)。初學(xué)階段不要追求“一定要塞進(jìn) 32KB 卡帶”之類(lèi)的極限但至少要確認(rèn)輸出文件沒(méi)有超出你的測(cè)試環(huán)境支持的上限。模擬器通常能直接運(yùn)行各種 ROM 大小可到了實(shí)機(jī)燒錄卡上超出范圍可能無(wú)法寫(xiě)入或根本無(wú)法啟動(dòng)。每次編譯后最好順手記錄 ROM 體積變化。如果上一次是 80KB改了幾個(gè)房間后飆到 200KB你要意識(shí)到地圖資源、tile 資源或代碼段可能超了需要優(yōu)先壓縮數(shù)據(jù)。不要等發(fā)布前才發(fā)現(xiàn)。5.2 在模擬器上驗(yàn)證什么模擬器能跑通不代表真機(jī)沒(méi)問(wèn)題。但模擬器能幫你過(guò)濾掉 90% 的邏輯錯(cuò)誤。在模擬器上我會(huì)按這個(gè)順序驗(yàn)證啟動(dòng)驗(yàn)證。冷啟動(dòng)能不能進(jìn)入標(biāo)題畫(huà)面或第一場(chǎng)景有沒(méi)有直接白屏黑屏。輸入驗(yàn)證。每個(gè)按鍵按下時(shí)角色是否有對(duì)應(yīng)動(dòng)作鍵盤(pán)是否沖突。碰撞驗(yàn)證。沿著墻走一遍看角色會(huì)不會(huì)卡死、穿墻、抖動(dòng)。房間切換驗(yàn)證。每到門(mén)位置背景和碰撞層是否切換正確玩家出生點(diǎn)是否合理。敵人驗(yàn)證。敵人移動(dòng)、攻擊、受傷、死亡四個(gè)狀態(tài)是否按預(yù)期切換。幀率驗(yàn)證。畫(huà)面是否明顯卡頓是否有對(duì)象閃爍。這里我最容易犯的錯(cuò)是“只在模擬器里按鍵跑兩分鐘就發(fā)布”。模擬器的穩(wěn)定幀率掩蓋了很多計(jì)時(shí)問(wèn)題。你要特地打開(kāi)幀步進(jìn)逐幀看角色和敵人的位置變化確認(rèn)每一幀里沒(méi)有“瞬移”或“跳越”現(xiàn)象。5.3 實(shí)機(jī)燒錄需要注意的事把 ROM 寫(xiě)到燒錄卡再插到真機(jī)第一次能亮起畫(huà)面那種感受和模擬器完全不一樣。但實(shí)機(jī)測(cè)試不是“炫一下”而是暴露真問(wèn)題的開(kāi)始??赡苡龅降牟町惏ㄋ⑿聲r(shí)序差異。模擬器畫(huà)面刷新方式和真機(jī)硬件時(shí)序不完全一致某些特效在模擬器上正常實(shí)機(jī)上會(huì)出現(xiàn)撕裂或閃爍。感覺(jué)“好像不影響”也要重視因?yàn)橥婕視?huì)注意到。按鍵掃描抖動(dòng)。真機(jī)的按鍵存在彈跳和長(zhǎng)按誤觸問(wèn)題模擬器上不會(huì)體現(xiàn)。你需要確認(rèn)長(zhǎng)按方向鍵時(shí)角色不會(huì)反復(fù)抖動(dòng)。燒錄卡兼容性。不同燒錄卡對(duì) ROM 頭字段、存檔類(lèi)型、SRAM 的支持不同。如果你的游戲用到存檔功能一定要在實(shí)機(jī)上驗(yàn)證“退出后重新進(jìn)入”是否保存成功。第一次上實(shí)機(jī)前別加新功能。只跑最小可玩原型重點(diǎn)看啟動(dòng)、移動(dòng)、切房間、攻擊判定這四個(gè)核心環(huán)節(jié)。實(shí)機(jī)上每發(fā)現(xiàn)一個(gè)問(wèn)題回到模擬器里構(gòu)造對(duì)應(yīng)場(chǎng)景修完后再燒錄驗(yàn)證。6. 常見(jiàn)問(wèn)題與排查順序6.1 花屏、亂碼、崩潰怎么查“花屏”這個(gè)詞在自制 GB 項(xiàng)目里非常寬泛。可能是地圖 tile 加載不全可能是調(diào)色板數(shù)據(jù)寫(xiě)錯(cuò)也可能是內(nèi)存越界寫(xiě)到顯存區(qū)域。我的排查順序是先看現(xiàn)象出現(xiàn)在什么時(shí)機(jī)。是固定某個(gè)房間花還是切換房間后花還是角色移動(dòng)到特定位置才花。再看 tile viewer。模擬器自帶視圖里當(dāng)前顯存中實(shí)際存放的 tile 圖案是什么。如果 tile 圖案本身是亂的問(wèn)題在資源加載如果 tile 圖正常但屏幕顯示亂問(wèn)題在地圖數(shù)據(jù)或 VRAM 寫(xiě)入。最后查地圖編碼。手動(dòng)把一個(gè)房間的碰撞層和視覺(jué)層分別打印出來(lái)看看數(shù)組邊界是否正確。數(shù)組越界在 C 里不會(huì)總是立刻奔潰經(jīng)常會(huì)表現(xiàn)為“屏幕某塊區(qū)域出現(xiàn)不屬于當(dāng)前房間的圖案”。不要一看到花屏就把鍋丟給工具鏈。自制項(xiàng)目很多花屏都來(lái)自資源索引寫(xiě)錯(cuò)比如地圖文件里寫(xiě)了一個(gè)超出 tile 總量的編號(hào)或者角色精靈圖片處理時(shí)把空字節(jié)算進(jìn)了有效數(shù)據(jù)。6.2 按鍵失效和角色穿墻按鍵失效先不要改解析代碼。先確認(rèn)你是“物理按鍵沒(méi)觸發(fā)”還是“觸發(fā)了但邏輯沒(méi)反應(yīng)”。方法是用模擬器的調(diào)試輸出把每次按鍵讀取到的狀態(tài)打印出來(lái)。如果打印顯示按鍵已經(jīng)被讀取但角色沒(méi)動(dòng)那就是狀態(tài)機(jī)里的移動(dòng)條件不滿(mǎn)足比如當(dāng)前玩家處于 attack 狀態(tài)時(shí)不響應(yīng)移動(dòng)輸入。這個(gè)設(shè)計(jì)本身沒(méi)問(wèn)題但如果 attack 狀態(tài)沒(méi)有正確結(jié)束就會(huì)出現(xiàn)典型的“方向鍵按了半天角色站在原地”的情況。角色穿墻的原因通常不是“碰撞代碼寫(xiě)得少”而是碰撞檢測(cè)用的坐標(biāo)和繪制用的坐標(biāo)不一致。假如繪制角色時(shí)用的是 float 坐標(biāo)碰撞檢測(cè)時(shí)把 float 轉(zhuǎn)成 int 并向下取整偏了一兩像素角色就可能在緊貼墻壁時(shí)卡進(jìn)墻縫。解決方法是統(tǒng)一坐標(biāo)類(lèi)型和取整規(guī)則并且碰撞檢測(cè)的目標(biāo)位置要考慮角色尺寸不能只檢測(cè)角色原點(diǎn)。GB 上我建議始終用整數(shù)像素坐標(biāo)幀內(nèi)只做整數(shù)運(yùn)算省內(nèi)存也避免這種“看得見(jiàn)但撞不上”的怪問(wèn)題。6.3 卡在房間切換和 ROM 超載房間切換卡死是常見(jiàn)問(wèn)題?,F(xiàn)象多半是走到門(mén)位置時(shí)“會(huì)暗一下”然后永久黑屏或卡在相同畫(huà)面。這是沒(méi)有正確裝載新房間資源導(dǎo)致的。排查順序從數(shù)據(jù)流去看玩家進(jìn)入門(mén)事件 - 程序讀取新房間 ID - 清理舊 tile - 寫(xiě)入新 tile - 重建碰撞層 - 設(shè)定玩家出生坐標(biāo) - 渲染背景。任何一環(huán)遺漏屏幕上就可能一直顯示舊地圖或者背景全空。我的建議是新房間加載函數(shù)里加一個(gè)調(diào)試標(biāo)志位每完成一步就改變屏幕上某個(gè)固定位置的顏色卡在哪個(gè)顏色就知道是哪一步?jīng)]做。ROM 超載是另一個(gè)問(wèn)題。如果你的構(gòu)建腳本開(kāi)始報(bào)地址越界或提示無(wú)法分配空間先不要急著寫(xiě)更短的代碼。最優(yōu)先做的事是檢查資源數(shù)據(jù)里有沒(méi)有重復(fù)內(nèi)容。同一個(gè)門(mén) tile 在不同房間重復(fù)存了很多份或者音效數(shù)據(jù)被鏈接兩次這類(lèi)冗余比代碼體積更容易頂爆 ROM。使用資源檢查工具把輸入文件大小列出來(lái)通常一眼就能看到哪個(gè)文件異常大。6.4 排查順序清單自制 GB 游戲的問(wèn)題排查我按這個(gè)順序走能解決絕大多數(shù)問(wèn)題看現(xiàn)象報(bào)錯(cuò)、花屏、卡屏、按鍵無(wú)反應(yīng)、速度異常先記錄復(fù)現(xiàn)方式??摧斎胛募窂绞欠裾_圖片格式是否符合工具要求地圖數(shù)據(jù)是否越界??喘h(huán)境工具鏈版本和系統(tǒng)是否匹配模擬器配置是否限制內(nèi)存或顯示??促Y源tile 是否重復(fù)地圖索引是否指向正確答案精靈數(shù)量是否超限??催壿嫚顟B(tài)機(jī)是否卡死碰撞檢測(cè)坐標(biāo)是否統(tǒng)一房間加載步驟是否完整。最后才懷疑工具本身先查社區(qū)文檔確認(rèn)你用的版本是否有已知限制。這套順序能避免你在“地圖編碼錯(cuò)誤”時(shí)去重寫(xiě)整段 AI 代碼也能避免在“模擬器不支持某項(xiàng)特性”時(shí)把自己折磨到凌晨三點(diǎn)。7. 一個(gè)更穩(wěn)的開(kāi)發(fā)路線(xiàn)建議如果你現(xiàn)在正打算動(dòng)手做《黑城堡 2》或類(lèi)似的自制 GBA 項(xiàng)目我建議按下面這條路線(xiàn)走。第一周不寫(xiě)玩法。先把工具鏈跑通編譯一個(gè)顯示“HELLO”或者簡(jiǎn)單移動(dòng)方塊的最小 ROM確認(rèn)模擬器能運(yùn)行、能調(diào)試、能看到循環(huán)日志。這個(gè)階段看起來(lái)不酷但它決定了整個(gè)項(xiàng)目是否可推進(jìn)。第二周到第三周做單人房間。一個(gè)地圖一個(gè)角色一個(gè)敵人。不需要敵人有多智能只要它會(huì)移動(dòng)并且在碰到玩家時(shí)造成傷害。核心目標(biāo)是驗(yàn)證“碰撞層 - 角色狀態(tài) - 繪制”這個(gè)鏈路沒(méi)有斷裂。第四周做房間切換。兩個(gè)房間之間有門(mén)走進(jìn)去房間數(shù)據(jù)能正常加載玩家位置正確不會(huì)閃退。這個(gè)階段你要開(kāi)始規(guī)劃地圖資源的分房管理。之后才逐步加內(nèi)容更多敵人、交互物件、音效、菜單、存檔。每加一個(gè)功能先在模擬器上跑穩(wěn)定再考慮實(shí)機(jī)驗(yàn)證。不要一口氣把“完整版設(shè)想”倒進(jìn)第一個(gè)可運(yùn)行版本否則你會(huì)分不清哪里是全新 bug哪里是上一版遺留問(wèn)題。如果只是學(xué)習(xí)模擬器加 GBDK 默認(rèn)配置就夠用。如果你想把這個(gè)作品放到實(shí)機(jī)上長(zhǎng)期玩那就盡早買(mǎi)一塊燒錄卡每完成一個(gè)里程碑就實(shí)機(jī)測(cè)一次。實(shí)機(jī)第一次跑通的感覺(jué)比模擬器上跑一百次都有說(shuō)服力也會(huì)讓你對(duì)“為什么必須理解硬件限制”有更真實(shí)的體會(huì)?!逗诔潜?2》這類(lèi)項(xiàng)目的終點(diǎn)不是做一個(gè)多復(fù)雜的游戲而是完整地回答一遍“在嚴(yán)苛平臺(tái)上做出一個(gè)小游戲”的全過(guò)程。等你走完一遍再回頭看網(wǎng)頁(yè)游戲或手機(jī)游戲很多“為什么卡頓”“為什么占內(nèi)存”“為什么資源要壓縮”的問(wèn)題你會(huì)有完全不同的理解。那時(shí)候你做的不只是《黑城堡 2》的續(xù)作而是更成熟的技術(shù)判斷力。