如何製作 Tilemap — 逐步指南與免費編輯器
動手做你自己的 tilemap
從零學會怎麼做 tilemap — tileset、網格、圖層、碰撞與自動拼接。照著步驟走,然後直接在瀏覽器裡免費做出來並試玩。
學會怎麼做 tilemap,說到底就是按正確順序做四個決定:儲存格大小、tileset 的版面配置、圖層堆疊,還有碰撞要放哪裡。這四個決定做一次,之後畫地圖就變成機械動作。
下面的步驟,就是我們在 Neta Studio 把一段文字描述變成可遊玩世界時用的順序:挑一組或生成一組 tileset、固定網格、先鋪地形再補細節、拆開圖層,最後讓自動拼接處理邊緣。這裡沒有任何一步需要安裝。編輯器直接在瀏覽器裡跑,下面提到的每個數字之後都能改,不必重畫地圖。
什麼是 Tilemap?(明確定義與示意圖)
Tilemap 是一格一格的索引,不是一格一格的圖片。每個儲存格存一個整數,指向 tileset 裡的某一塊 tile,渲染器查出這個索引,把 tile 畫在儲存格的位置上。一張 20×12、儲存格 32 px 的地圖,就是 240 個整數,覆蓋 640×384 像素。
這層間接就是重點。一塊草地 tile 在圖集裡只存一次,卻被上百個儲存格引用,所以地圖檔案很小、載入很快。也因為 tilemap 是資料而不是美術圖,你可以用雜訊函式生成它、把它當文字編輯、用 Git 比對兩個版本,或在鏡頭移動時分塊串流載入。
本頁最上方那張圖,是用我們自己的 tile 鋪在 12×8、儲存格 32 px 的網格上。草地填滿內部,沙地沿著左邊與底邊延伸,水放在右下角沙地結束的地方。當資料來讀:第 3 欄、第 0 列的儲存格存的是草地索引,第 0 欄、第 7 列存的是水角的索引,其餘每一格都是普通的沙地索引。原點在左上,x 往右遞增,y 往下遞增。如果你的引擎原點在左下,地圖會上下顛倒,除非你把列索引換成「總列數 − 1 − y」。
- 網格欄與列,在地圖建立時就固定。同一種鏡頭縮放下,20×12 和 40×24 的地圖表現完全不同。
- 儲存格大小單一儲存格的像素尺寸:16、32、48 或 64。它必須和 tileset 的 tile 尺寸完全一致,否則取樣會落在 tile 中間。
- tile 的集合索引所指的那張圖,加上一份說明它有幾欄幾列的定義。
- 索引圖層每一層、每一格存一個整數。地面層是密集的,細節層大多是空的,碰撞層存的是旗標,不是圖。
什麼是 Tileset?(以及它和 Tilemap 差在哪)
tileset 是那本圖畫書,tilemap 是指向它的那一頁引用。tileset 擁有像素:一張圖集,加上一條規則說明這張圖集是 8 欄 32 px 的 tile。tilemap 擁有擺放:哪個索引坐在哪一層的哪一格。
這兩個詞經常被混用,因為多數編輯器把它們並排放在同一個面板,也因為有些單檔格式會把這個切分藏起來。在 Tiled 裡切分很明確,tileset 檔和地圖檔是分開的。在 Unity 裡,Tile 資源存 sprite,Tilemap 元件存儲存格。在 Godot 裡,TileSet 和 tilemap 圖層是各自獨立的資源。
實務上的結果是:改 tileset 會改到每一張引用它的地圖,改 tilemap 只改一個關卡。把 32 px 的圖集換成 48 px 的,每個索引照樣解得開,但每張地圖現在畫出來的縮放都不一樣,邊緣還可能取樣到隔壁的 tile。把 tileset 當成有版本號的對外介面來看待。
| 面向 | Tileset | Tilemap |
|---|---|---|
| 它存什麼 | 每一塊 tile 的像素資料 | 每一層、每格一個索引 |
| 磁碟上是什麼 | 一張圖,加上儲存格大小的定義 | 一串整數,加上對 tileset 的引用 |
| 誰在編輯它 | 美術 | 關卡設計師或生成器 |
| 什麼時候會改 | 美術風格或縮放改變時 | 版面改變時,包括每一次試玩微調 |
步驟 1:挑一組 tileset,或自己做一組
你有兩條路:拿現成的圖集,或自己生成。現成的比較快,但你要繼承它的色盤、它的儲存格大小、它的授權。在拿它鋪四十張地圖之前先確認授權,也要確認它是網格圖集而不是打包圖集。打包圖集存的是每塊 tile 的矩形座標,不是統一的儲存格,索引會變難做,自動拼接也會很彆扭。
如果地形必須配合特定色盤,就自己生成。Agent Sprite Forge 能從一段文字描述產出 tile 圖集,儲存格大小一致,直接消掉最常見的錯位來源:圖集裡有一塊 tile 寬 33 px,只因為美術把高光畫到格子外面去了。
不管走哪條路,第一輪不是畫圖,是量測。用 100% 縮放打開圖集,數欄數和列數,把儲存格大小寫在團隊找得到的地方。
- 統一的儲存格大小每一塊 tile 寬高都一樣,包括大部分是空白的 tile。32 px 圖集裡只要有一塊 33 px 的 tile,而且匯出工具是按閱讀順序打包,它之後的每個索引都會位移。
- 真正的透明要 alpha,不要白色。圖集如果是不透明白底,在瀏覽器裡會在你的天空上畫出白色方塊。
- 有記錄的網格記下欄數、列數,以及有沒有留邊距。索引算法是「列 × 欄數 + 欄」,從 0 開始,由左到右、由上到下。
- 先決定要不要留縫有留縫的圖集會在儲存格之間加 1 或 2 px 來減少材質滲色,代價是取樣算法要多加一個偏移。第一筆畫下去之前就先決定用哪一種。
步驟 2:決定 tile 大小與網格
儲存格大小由三件事決定:玩家實際遊玩時的縮放、你的目標渲染解析度,以及你願意畫多少美術。16 px 的 tile 在 1920 px 寬的視口上,橫向有 120 格,一個畫面塞進很多關卡,但幾乎沒空間畫細節。64 px 的 tile 只有 30 格,像素多到能畫出一棵看得懂的樹。
真正硬的限制是算術。儲存格大小必須整除圖集。256 px 寬的圖集、32 px 儲存格剛好 8 欄;48 px 會變成 5.33 欄,最後一欄只有一小塊,它之後的每個索引都會偏移。圖集尺寸不是美感選擇,它是儲存格大小的倍數。
用整數縮放。如果視口不是網格的整數倍,儲存格會落在半像素上,濾波會取樣到隔壁兩塊 tile,整張地圖會出現一條一像素的接縫。兩端都要修:引擎用最近鄰濾波,鏡頭縮放用整數。
| 儲存格大小 | 適合什麼 | 要注意什麼 |
|---|---|---|
| 16 px | 小尺寸角色、每畫面高格數、用程式生成的地圖 | 幾乎沒空間畫細節;三色的樹看起來只是一團 |
| 32 px | 俯視角與平台遊戲地形的中間值 | 美術必須畫在 32 px 網格上,否則和其他 tile 擺在一起會很突兀 |
| 48 px | 較厚重的俯視角風格,道具看得清楚 | 每畫面格數變少,稀疏的地圖會顯得空 |
| 64 px | 細節多的美術、低格數、要擠出成 3D 的地圖 | 如果每格用一個碰撞器,碰撞手感會偏粗 |
步驟 3:先鋪地面,再補細節
分輪次畫,永遠先畫地形。地形是你之後會改的東西,裝飾是你不想移動的東西。先擺了六十棵樹,接著把海岸線往左移四格,那六十棵樹全站在水裡。
從單一 tile 開始。用同一個草地索引把整張地圖填滿,在遊戲縮放下看輪廓,然後才讓自動拼接處理邊緣。細節等形狀定案之後再加,碰撞等細節之後再做,因為發現某個道具可以走過去,那是碰撞要改,不是重畫。
玩家看得到的每一格都要畫。空格預設會顯示渲染器在地圖後面畫的東西,那通常不是你要的背景。在鏡頭上設一個背景色,比依賴清空過的畫布安全。
01
建立地面層,用同一個地形索引把整張地圖填滿。先不要自動拼接。
02
鋪形狀:先畫水,再畫崖,各用單一 tile。完全不管邊緣。
03
在遊戲縮放下檢查輪廓,不要用 400%。現在讀不出形狀,之後也讀不出來。
04
輪廓定案後,對每個地形跑一次自動拼接,之後每次動地形都要重跑。
05
加細節層:道具、貼花、草叢。保持稀疏,稀疏看起來才像刻意安排的。
06
碰撞層最後畫,依你實際留下的細節,而不是你原本計畫的細節。
步驟 4:加圖層、深度與碰撞
tilemap 一旦拆成圖層,就不再是平的。最小可用的堆疊是三層:密集的地面層、稀疏的細節層,以及完全不放圖的碰撞層。如果地圖裡有任何東西該遮住玩家,那疊在角色之上的覆蓋層要早點加。
深度來自同一個網格。俯視角地圖按每個物件的底邊排序:腳踩得最低的最後畫。對 tile 來說這是免費的,因為儲存格的 y 已經編碼了位置。對會移動的角色,你需要逐物件的排序,加上一個決定性的平手規則,通常是物件的 y 加上它在儲存格內的小數偏移。少了這一條,同一列的兩個物件會在畫格之間閃爍。
碰撞對應到網格上,就是一份同尺寸的平行陣列。在 tileset 裡給每塊 tile 一個實心旗標,畫牆的同時也就標了實心,再用碰撞層做單張地圖的例外覆寫。逐格旗標表達不了斜坡、單向平台,或儲存格內的薄牆。那些屬於疊在網格之上的獨立物件,不屬於 tilemap。
- 地面層密集:每一格都存一個索引。這是唯一能安心自動拼接的圖層。
- 細節層稀疏,空的地方透明。樹、石頭、貼花。這裡的東西不該不小心擋住移動。
- 覆蓋層畫在角色之上:樹冠、橋、屋頂。任何該遮住玩家的東西。
- 碰撞層不放圖。一個和地圖同尺寸的布林陣列,初始值來自 tileset 的實心旗標,例外的地方手動改。
步驟 5:自動拼接與 47 塊 tile 組
自動拼接就是那種會安靜出錯的部分。一塊地形 tile 有八個鄰居,就有 2^8,也就是 256 種相鄰組合。角只有在兩側相鄰都填滿時才重要,這個對稱性把 256 種情況收斂成 47 塊唯一的 tile。這就是為什麼多數引擎和編輯器每種地形都吃一組 47 塊的 blob 圖集,並按一份有記錄的版面排列。
你也會遇到 16 塊的組合,它只看四個邊鄰居、忽略角,還有 2x2 迷你 tile 組合,每格由四個四分之一塊畫成,13 到 16 塊迷你 tile 就能涵蓋所有情況。16 塊的組合比較好畫,但角會很鈍。迷你 tile 的做法用的圖更少,但強迫你把每種地形都拆成四等分來畫,會讓像素畫變軟。
怎麼做 tilemap 才能自動拼接又不留縫
遮罩就是整個機制。對每一格,用它的八個鄰居組成一個 8 位元整數:位元 0 是北、位元 1 是東北、位元 2 是東,依此類推順時針排。挑一個順序,寫在程式碼旁邊,然後永遠不要改。遮罩差一個位元還是會產出看起來合理的地形,這就是為什麼兩邊慣例不一致可以存活好幾個月,直到有人發現角是錯的。
然後把遮罩轉成 tile 索引。有兩種做法:一張完整的 256 項查表,生成起來輕鬆,除錯也容易,因為你可以把它印出來;或是 47 項的表加上一個對稱函式。除非目標有記憶體限制,否則就用完整查表。它直接消掉一整類的角錯誤。
接自動拼接之前,值得先知道的幾種失敗模式
地圖邊緣的接縫,來自對超出邊界的儲存格處理不一致。一次決定好地圖外的世界是空的、還是同一種地形,然後每個地形都套同一條規則。
第二種常見的失敗是覆寫。自動拼接會重寫它管轄的儲存格,所以你手畫的、為了特定造型的單塊 tile,下一輪就不見了。要嘛在自動拼接後鎖住地形層,要嘛把手動覆寫存在另一份最後才套用的清單裡。
01
在圖集裡為每種地形保留一塊 47 塊的區域,各種地形各自成塊,不要交錯擺放。
02
定義鄰居位元順序,並把它記在消費這份遮罩的程式碼旁邊的註解裡。
03
對每一格,用八個鄰居算出遮罩,超出邊界的格子按你選的規則處理。
04
拿遮罩去查那張 256 項的表,回傳該地形的 47 個索引之一。
05
某一格變動時,只重算它自己加上八個鄰居。每一筆筆刷都重建整張地圖,就是常見的效能問題。
06
地形滿意之後就凍結地形層,否則你手放的 tile 下一輪就會被覆寫。
可以直接打開來玩的 Tilemap 範例
讀一張能跑的地圖,比讀一段描述學到更多。The Cyclops' Island 是一張可遊玩的地圖,建在單一地地形組上,另有獨立的碰撞層。沿著海岸線走,看沙地轉水面的地方 tile 怎麼換:那些就是實際用到的 47 種 blob 情況。
Mini World 是比較小的例子。打開它看圖層堆疊,地面、細節與覆蓋,也看看要多少塊細節 tile,一張地圖才會讀起來像完成了,而不是空的。
3D game builder 用同一個網格擠出成立體。每一格變成一個方塊,碰撞層變成垂直的牆,y 排序變成 Three.js 場景裡真正的深度。2D 地圖如果是一致的,轉 3D 幾乎不花你什麼力氣;圖層如果混在一起,擠出之後馬上就露餡。
- The Cyclops' Island一張可以走進去的地圖,用來在遊玩速度下、而不是編輯器縮放下,研究自動拼接後的地形邊緣。
- Mini World一張小網格,不用捲動就能檢視圖層堆疊。
- 3D game builder同一張 tilemap 擠出成方塊,碰撞層變成幾何,深度變成真的。
Tileset 範本下載(PS / Aseprite 網格)
範本是畫圖的輔助,不是必需品。它是一張透明畫布,每一格有參考線,往內一像素有安全區標記,最上面一列與最左一欄有索引編號,讓你畫的時候看得出來索引會落在哪。畫在它上面,匯出前把它藏起來。
範本要配合專案。512×512 的畫布裝得下 16×16 格、每格 32 px。換成 16 px 儲存格,同一個檔案裝得下 32×32 格,這時候參考線要調淡,因為在那種密度下參考線會蓋掉圖。
01
建立畫布,尺寸用儲存格大小的倍數:32 px 儲存格就用 512×512,16 px 儲存格就用 256×256。
02
在 Aseprite 裡把網格大小設成你的儲存格大小,開啟貼齊,tile 才會落在完整的格子上。
03
在 Photoshop 裡用參考線版面,欄數對齊你的圖集,邊距與間距都設 0,或每 32 px 手動放一條參考線。
04
把範本留成獨立圖層,帶著參考線、1 px 內縮框與索引標籤,工作時壓在圖上方。
05
關掉抗鋸齒與工具平滑。帶抗鋸齒匯出的像素畫,邊緣沒辦法乾淨拼接。
06
隱藏範本圖層,以 PNG-24 含透明通道匯出,保留分層檔,下次編輯範本還在。
走進它所屬的那個世界
精靈沒有地方站。最後這一步是別人沒給的:把你剛做好的圖集放進一個有人能走來走去的世界——一座小島、一顆行星、一個下雨的街角。這個網站上的每個世界都是從一段文字描述做出來,發佈成一頁能玩的東西。
常見問題
什麼是 tilemap?+
一張索引網格,每一格指向 tileset 裡的一塊 tile。渲染器查出索引,把 tile 畫在儲存格位置上,所以 20×12、32 px 的地圖就是 240 個整數,覆蓋 640×384 像素。
tilemap 和 tileset 差在哪?+
tileset 是那張圖加上它的儲存格大小;tilemap 是引用它的擺放資料。改 tileset 會改到每一張用到它的地圖,改 tilemap 只改一個關卡。
tilemap 最適合用多大的 tile?+
任何能整除你的圖集、而且縮放是整數倍的大小。32 px 是 2D 地形的常見中間值,16 px 適合小角色與高格數,64 px 適合細節多、每畫面格數少的美術。
什麼是自動拼接,我一定需要 47 塊的組合嗎?+
自動拼接會依鄰居決定某一格要用哪一塊邊與角。47 塊的 blob 組合在對稱之後涵蓋全部 256 種八鄰居情況;16 塊只看邊的組合比較好畫,但角會很鈍。
我可以在瀏覽器裡免費做 tilemap 嗎?+
可以。Neta Studio 的編輯器在瀏覽器裡跑,你可以從一段文字描述生成地圖,或親手畫一張,不用安裝任何東西就能玩。
要怎麼把 tilemap 匯入我的引擎?+
把圖集匯出成 PNG,擺放資料匯出成索引的 JSON 或 CSV,然後確認儲存格大小、欄數、原點都和引擎預期一致。原點是左下而不是左上,地圖會上下顛倒。
tilemap 可以做等角嗎?+
可以。儲存格變成菱形,排序改成按「列 + 欄」而不是按 y。高度需要第二層,碰撞會變成錯位的網格,不再對應單純的矩形陣列。
要怎麼從零開始做 tile?+
用最終的儲存格大小來畫,絕對不要畫大再縮,每塊 tile 先在 3×3 的自身拼接裡測試,再繼續畫圖集剩下的部分。邊緣重疊留一像素,用一組小而定案的色盤,這樣不同時間做的 tile 才對得起來。
回到 3D Game Builder
31 個完成的 3D 世界、遊戲與場景都在同一頁——每一個都能玩。