最近在 MapleStory Worlds(楓之谷世界,下稱 MSW) 做一個楓之谷風格的橫向小世界:走跳、打怪、升級、裝備、城鎮和任務都有。專案變大之後,我發現最常做的不是寫新程式,而是「改一個數字、加一隻怪、把歪掉的格子移回去」。
這篇是我給自己(也給想試試看的人)的操作手冊,只講怎麼調整、怎麼使用。
為了不公開專案裡的素材與資料,文中所有編號、名稱、資源碼都是示範用的假資料,圖也是另外畫的示意圖。照著做時,請換成你自己專案裡的值。
這篇會依序講:
- 改檔之後一定要做的事
- 專案資料夾怎麼看
- 資料表(CSV)的規則和關係
- 實作:新增一件裝備
- 實作:新增一隻怪物,讓牠掉這件裝備
- 實作:把裝備放進商店
- 實作:新增一張地圖並接上傳送門
- 介面跑位怎麼調
- 常見問題排查
- 搭配 AI 助手開發的守則
1. 改檔之後一定要做的事
不管你改的是資料表、地圖還是介面,都照這個順序:
- 如果正在 Play,先停止。
- 在 Maker 按 Refresh,讓它重新讀檔。
- 再 Play,走到現場實際確認。
遊戲執行中會把資料留在記憶體裡,改完「畫面沒變」,九成是少做了其中一步。我後來養成習慣:每改一個東西就跑一次這三步,不要一次改十個地方再一起測,出錯時才找得到是哪一個。
2. 專案資料夾怎麼看
| 資料夾 | 放什麼 | 能不能改 |
|---|---|---|
RootDesk/MyDesk/ |
自己寫的 .mlua 腳本、.model、資料表 CSV |
✅ 主要工作區 |
map/ |
.map 地圖檔 |
✅ 但要透過 Maker 或 Builder |
ui/ |
.ui 介面檔 |
✅ 但要透過 Maker 或 Builder |
Global/ |
引擎預設、世界設定 | ⚠️ 只能就地修改,不要新增或刪除 |
Environment/ |
.d.mlua API 定義 |
❌ 只讀,拿來查 API |
新手最容易踩的三個雷:
- 新檔案只能放在
RootDesk/。 放在Global/的新檔,Maker 不會認得,也不會報錯,就是「不存在」。 .mlua要跟同名的.codeblock一起存在。.codeblock是自動產生的描述檔,不要手改;只有.mlua時,腳本不會被註冊。.map、.ui、.model不要用文字編輯器手改 JSON。 這幾種檔裡有大量 UUID 和路徑互相參照,手改很容易壞,而且壞了也不一定有錯誤訊息。
3. 資料表(CSV)的規則和關係
我把常改的數值(裝備、怪物、商店、掉落)都放在 DataSet 的 CSV 裡。好處是調平衡不用碰程式。
每張表都是一組兩個檔:
XxxDataSet.csv:真正的資料,改這個。XxxDataSet.userdataset:只負責讓 Maker 認得這張表的名字,不要動。
3.1 改 CSV 的共通規則
- 第一列是欄位名,不要改、不要刪、不要多空白。
- 新增資料時,從最接近的一列整列複製到最後面再改,比從零開始打安全很多。
- 存成 UTF-8,不然中文會變亂碼。
- 每一列的逗號數量要跟表頭一樣,空欄位也要留著逗號。
- 同一個編號全表只能有一列。
- 改之前先備份原檔。
3.2 用 Excel 編輯要特別小心
Excel 打開 CSV 很方便,但它會「好心」幫你改資料:
- 前面的 0 會被吃掉:
0190001會變成190001,對遊戲來說是另一個編號。 - 長數字會變成科學記號,例如
1.01E+06。 - 存檔編碼可能不是 UTF-8,中文名稱整排變亂碼。
我現在都用 VS Code 直接改 CSV。真的要用 Excel,就用「資料 → 從文字/CSV」匯入,把編號欄設成「文字」,存檔時選「CSV UTF-8」。
3.3 資料表怎麼串在一起
這張圖是後面所有實作的基礎。資料表之間靠編號對應:
- 地圖上那隻怪的 Monster 元件,用
MobId去怪物表找數值。 - 掉落表的一列寫「哪隻怪(
MobId)掉什麼(ItemId)」。 - 商店表的一列寫「哪位 NPC 賣什麼(
ItemId)、賣多少」。
所以只要其中一邊打錯一個字,就會對不上,而且通常不會報錯,只是「沒效果」。
4. 實作:新增一件裝備
目標:做一頂「練習頭盔」,防禦 20,所有職業都能穿。
步驟 1:複製一列
打開裝備表,找一件同部位的裝備(例如另一頂帽子),整列複製到最後面。表頭大概長這樣(示範):
Id,Name,Slot,ReqJob,ReqLevel,ReqStr,ReqDex,ReqInt,ReqLuk,Str,Dex,Int,Luk,Pad,Mad,Pdd,Mdd,Hp,Mp,...,Price,AvatarRuid,AvatarCat,Icon
步驟 2:改身份欄位
1009999,練習頭盔,Cap,0,1,...
Id:換一個還沒用過的編號。我的專案裡裝備用固定的一段號碼區間,程式靠這個區間判斷「這是裝備」,所以新編號要落在同一段。Name:畫面上的名字。Slot:穿到哪一格,必須用程式認得的英文,大小寫要一樣,例如Cap(帽子)、Coat(上衣)、Weapon(武器)。
步驟 3:設定誰能穿
| 欄位 | 意思 |
|---|---|
ReqLevel |
需要等級 |
ReqStr/ReqDex/ReqInt/ReqLuk |
需要的四屬 |
ReqJob |
職業限制,用數字加總 |
ReqJob 用的是「位元加總」,每個職業一個數字:
| 數字 | 職業 |
|---|---|
0 |
全職業 |
1 |
劍士 |
2 |
法師 |
4 |
弓箭手 |
8 |
盜賊 |
16 |
海盜 |
多個職業就相加:劍士+法師是 3,弓箭手+盜賊是 12。
步驟 4:設定能力值
穿上後加的數值,照欄位填就好。常用的幾個:
| 欄位 | 意思 |
|---|---|
Pad/Mad |
物理/魔法攻擊 |
Pdd/Mdd |
物理/魔法防禦 |
Str/Dex/Int/Luk |
四屬加成 |
Hp/Mp |
最大 HP/MP |
Price |
賣回 NPC 的價格(商店買入價在商店表) |
練習頭盔就是把 Pdd 那格改成 20。
步驟 5:外觀和圖示
AvatarRuid:穿上後角色身上的造型。AvatarCat:造型的種類。衣服、武器這類會被程式拿來過濾,填錯不會報錯,只是穿上去身上沒換裝。Icon:背包、裝備欄、掉落物用的小圖。
最常見的需求是「背包圖不好看想換」:只改 Icon 就好,不用動身上的造型。
步驟 6:拿到手上測試
表裡有這一列,不代表地圖上撿得到。我在專案裡做了一個 GM 指令,在聊天框輸入:
/item 1009999
就會直接塞進背包。打開背包和裝備窗,確認三件事:圖示正常、說明框的數字對、穿得上身。
如果裝備有「卷軸強化」機制,畫面數字=表上的基礎值+這一件自己的強化值。改 CSV 不會清掉玩家已經強化的部分。
5. 實作:新增一隻怪物,讓牠掉這件裝備
目標:做一隻「練習木樁」,血量 30、經驗 8,打死有機率掉練習頭盔。
步驟 1:在怪物表新增一列
一樣從最像的一隻怪整列複製,改成新的 MobId:
MobId,Name,Level,MaxHp,Pad,Pdd,Exp,...
0190001,練習木樁,3,30,18,0,8,...
MobId是文字:0190001和190001是兩個不同的編號,前面的 0 要保留。- 想沿用原本那隻怪的外觀,就把外觀資源碼和身體範圍那幾欄原樣抄過來。
- 「碰到會不會扣血」通常由兩個欄位一起決定(例如「不攻擊」旗標和「身體攻擊」旗標),兩個都要設對。
步驟 2:放到地圖上
- 在 Maker 的 Hierarchy 找一隻已經在地圖上、外觀想沿用的怪。
- 複製它,拖到新的位置。
- 選中複製出來的那隻,打開 Monster 元件,把
MobId改成0190001。
步驟 3:搞懂遊戲怎麼「對表」
這是我踩過最久的坑:新怪放上去,血量卻還是舊怪的。原因是表裡沒有這個 MobId(或打錯),遊戲就改用外觀去反查,結果對回「長得一樣的那隻舊怪」。
所以新怪一定要 表和元件都填同一個編號,一字不差。
步驟 4:讓牠掉裝備
在掉落表加一列:
MobId,ItemId,Chance,Min,Max
0190001,1009999,1500,1,1
Chance我用「百萬分之幾」,比較好微調:1500就是 0.15%。- 如果程式對裝備另外乘了掉率倍數(例如 ×10),實際機率要再乘上去,也就是 1.5%。
- 裝備一次掉一件,
Min、Max都填1。
步驟 5:只想讓「某一隻」變強
不要去改共用的那一列,地圖上所有同編號的怪會一起變。給牠一個新的 MobId 和新的一列才對。
6. 實作:把裝備放進商店
在商店表加一列:
NpcId,ShopId,ItemId,Price,Position
9000001,1,1009999,500,10
NpcId、ShopId:抄同一位 NPC 現有的值。Price:玩家買入的價格。Position:在清單裡的排序,數字小的排前面。
Refresh 之後找那位 NPC 打開商店,練習頭盔就會在清單裡。
7. 實作:新增一張地圖並接上傳送門
7.1 先選地圖模式
模式決定了整套物理。 配錯 Body 不會報錯,角色就是不會動,偶爾會看到 [LEA-3004] MissingComponent 這類訊息。楓之谷風格的橫向地圖選模式 0。
模式要在建立前決定,之後再切換會清掉地形,所有會動的實體也要重新檢查。
7.2 從模板建立地圖
不要複製舊地圖再改檔名。模板建立會重新產生 UUID 和內部路徑;用複製的,兩張地圖常會共用舊的 ID,出現很難查的怪問題。
我的專案用 MapBuilder 從模板建立(示範):
MapBuilder
.fromTemplate(MapBuilder.templatePath('maple'), 'myfield01')
.write('map/myfield01.map')
地圖 ID 只用英文和數字,例如 myfield01。
7.3 註冊到 Sector
只有 .map 檔還不夠,要把它加進世界的 Sector 設定:
"entries": [
"map://townmap",
"map://myfield01"
]
這裡要寫 map://。漏了這步,傳送時會說找不到地圖。
7.4 畫地形、放出生點
- 在 Maker 開新地圖,用地形筆刷畫地板和平台。
- 模式 0 要確認平台有 Foothold,角色才站得住。
- 從正常的地圖複製一個
SpawnLocation過來,放在平台上方。
座標單位是「世界單位」,不是像素。 1 單位=100 像素,PC 畫面大約 12.8 × 7.2 單位。如果你習慣填像素,填了 400,那個東西已經在 4 萬像素外了。
7.5 做雙向傳送門
只做「舊地圖 → 新地圖」的門,玩家進去就出不來。至少兩扇:
舊地圖的去程門
DestinationMapId = myfield01
DestX、DestY = 新地圖裡的安全落點
AutoEnter = false
新地圖的回程門
DestinationMapId = townmap
DestX、DestY = 舊地圖裡的安全落點
AutoEnter = false
三個重點:
- 傳送門的目的地寫純名稱
myfield01;Sector 註冊才寫map://myfield01。兩個地方規則不同。 DestX、DestY是目的地的落點,不是這扇門自己的位置。- 落點不要跟門重疊;
AutoEnter關掉、讓玩家按 ↑ 進門,才不會兩扇門互相彈來彈去。
7.6 修改既有地圖
| 想改的 | 去哪改 |
|---|---|
| 移動怪物、NPC、傳送門、裝飾 | Maker Hierarchy 直接拖 |
| 平台、地板、牆 | Maker 的地形筆刷 |
| 傳送目的地 | 傳送門元件的目的地與落點座標 |
| 地圖要不要被載入 | Sector 設定 |
不要去移動承載 TileMap 的地形容器,它被鎖在原點,改了也會被還原。
8. 介面跑位怎麼調
先判斷是「哪一層」歪掉,再去改對應的地方:
| 你看到的 | 改哪裡 |
|---|---|
| 整扇視窗偏了、跑出畫面 | 改視窗本身的位置,子格子會跟著動 |
| 只有某一格對不上底圖 | 改那一格的 X、Y |
| 只有某一件物品的圖歪,其他件正常 | 是圖本身的問題,換那件裝備的 Icon |
| 背包格子一起偏,或越往下越歪 | 改背包排版公式的起點與間距 |
| 說明框被聊天列蓋住 | 調整說明框的底部保留高度 |
8.1 底圖放大 2 倍時怎麼換算格子位置
我的裝備窗底圖是原圖放大 2 倍顯示,格子的錨點在左上角,所以:
介面 X = 原圖上格子左上角的 X × 2
介面 Y = −(原圖上格子左上角的 Y × 2)
例如原圖上某一格在 (40, 30),介面座標就是 (80, −60)。Y 取負號是因為介面座標「往上才是正的」。
8.2 只偏一點點的時候
以一格目前在 (80, −60) 為例:
| 畫面上的樣子 | 改成 |
|---|---|
| 偏右 8 | X = 80 − 8 = 72 |
| 偏左 8 | X = 80 + 8 = 88 |
| 偏上 6 | Y = −60 − 6 = −66 |
| 偏下 6 | Y = −60 + 6 = −54 |
口訣:往左 X 減、往右 X 加、往下 Y 減、往上 Y 加。 一次只移 4~8 像素,Refresh 再看。
8.3 背包這種排成格子的
背包不是一格一格寫死的,而是用「起點+間距」算出來:
第 n 欄的 X = 起點 X + (n − 1) × 欄距
第 m 列的 Y = −(起點 Y + (m − 1) × 列距)
全部一起偏,改起點;越往右(或越往下)越歪,改欄距(或列距)。不要去逐格改,下一次重排還是會被公式蓋掉。
9. 常見問題排查
| 症狀 | 先檢查 |
|---|---|
| 改了 CSV,遊戲裡還是舊數字 | 有沒有停止 → Refresh → 再 Play |
| 新怪血量還是舊怪的 | 表和地圖上元件的 MobId 是否一字不差(含前面的 0) |
| 裝備穿上了,身上沒換裝 | AvatarCat 是否填對、AvatarRuid 是否有值 |
| 背包裡的圖特別大或偏位 | Icon 是否空白,正在用造型縮圖 |
| 中文名稱變亂碼 | CSV 是否存成 UTF-8;是不是被 Excel 改過 |
| 傳送時說找不到地圖 | Sector 是否有 map://名稱;傳送門是否寫純名稱 |
| 進新地圖後一直往下掉 | 落點下方有沒有 Foothold,Y 是否放在平台上方 |
| 角色完全不動 | 地圖模式和 Body 是否配對 |
| 傳送後馬上被送回去 | 落點和門重疊,或 AutoEnter 開著 |
| 東西看不見,也沒有錯誤 | SpriteRUID 是不是空字串;座標是不是填成像素 |
| 新加的腳本沒作用 | 檔案是否放在 RootDesk/;.codeblock 是否存在 |
10. 搭配 AI 助手開發的守則
我開發時會搭配 AI 助手,並在專案根目錄放一份 AGENTS.md,把 MSW 的規則寫給它看。實際用下來,最有用的是這幾條:
- 結構化檔案一律透過 Builder 修改:
.map、.ui、.model不准直接改 JSON。 - 沒有實際執行就不能說「可以了」:要它真的 Play、讀 log 再回報,不能只看程式碼就宣稱正常。
- 在關鍵位置留驗證 log:例如
log("[VRF] wave spawned n=3"),驗證時只讀這類 log,一次確認全部改動。 - 大功能先寫規劃文件:用 GDD 和分階段文件切開需求,每段做完、驗收完再做下一段。
- 生成物件時要指定 parent:
SpawnByModelId的 parent 要給目前的地圖實體,不能給nil。
給 AI 的指令越具體越好,幾個我常用的寫法:
請建立模式 0、ID 為 myfield01 的新地圖,加入 Sector,
並在 townmap 與 myfield01 之間做雙向傳送門,落點放在平台上方。
在怪物表新增 MobId 0190001「練習木樁」(複製最接近的一列),
在 myfield01 放 3 隻,並讓牠以 0.15% 掉落裝備 1009999。
做完請 Play 驗證,回報 [VRF] log。
裝備窗的頭盔格往右偏了大約 8 像素,只調整那一格,不要動整個視窗。
改完之後的檢查清單
- 停止 Play → Refresh → 再 Play
- 新資料的編號沒有重複,CSV 逗號數量正確、UTF-8 存檔
- 地圖上實體的編號和資料表一字不差
- 用 GM 指令拿到新物品:圖示、說明、穿上身都正常
- 新怪:血量、經驗、掉落都符合設定
- 新地圖:去程、回程都走過,落點站得住、怪不會掉出地圖
- 介面:點擊區蓋住底圖的空格,圖示在格子正中
遇到 MSW 本身的問題、文件也查不到時,官方 Discord 社群問得到人:https://discord.com/invite/maplestoryworlds。







