淺談RPG Maker XP自動地圖元件的繪製原理
序:最近剛好想寫個類似RMXP的地圖編輯器,遇到的第一個問題就是自動地圖元件的繪製問題。老實說,我不知道這東西到底叫什麼(特別是英文叫什麼),只知道RMXP翻譯過來就是自動地圖元件,而且魔獸的地圖編輯器也用到這種技術,但我在網上卻始終找不到相關的資料。其繪製原理其實很清晰,就是用戶給出指定規格的地圖元件資源,編輯器將其拆分,在繪製時再根據繪圖上下文來進行拼接。但具體的過程以及拆分和拼接原理卻很少有人提及,這裏我整理了一下。
1.RMXP的自動地圖元件規格
首先介紹下RMXP的自動地圖元件規格,其中第一格和第二格都是展示用的,並不用在具體的拼接上。話說我也是剛搞懂的。
圖1
真正用在拼接的就是剩下的內容,這裏先將其按下圖拆分開
圖2
這裏的每一個小格就是最後用於拼接的格子,我們暫且叫它小元件,如圖每一個小元件都有一個編號
雖然編號爲0,1,2,3,6,7,8,9的小元件都不會用到,但這樣可以方便由編號來進行定位小元件的矩形
計算編號爲i的小元件在圖中的矩形的方法爲:
size = 16 // 小元件的寬高
row = i / 6 // 小元件所在的行
col = i % 6 // 小元件所在的列
(col * size, row * size, size, size)
2.分析所有拼接情況
在RMXP中,雙擊自動地圖元件,即可以打開自動展開元件的對話框。裏面顯示的就是所有拼接情況。
圖3
同樣的,我們給每一種情況定一個編號
圖4
注意,最後2個情況是一樣的,所以總共有47種情況(編號從0開始)
每一種情況都是由4個小元件拼成的。這裏先不急着確定每一種對應哪4個小元件。
先來分析下到底是哪47種情況。
假設我們現在正在用RMXP的地圖編輯器來繪製自動地圖元件,如下圖,我們打算在位置4繪製一個自動地圖元件
圖5
那麼怎麼判斷繪製出來的是哪個情況呢?很簡單,根據周圍的1,2,3,5,6,7,8格子是否有自動地圖元件來確定位置4的繪製情況。(這裏要注意,影響位置4繪製情況的是周圍8個格子是否有自動地圖元件,而每個格子具體是哪個情況並不影響)
按照排列組合公式,可以算出8個格子的出現組合有256種,這比47大多了。爲什麼呢?
這是因爲RMXP並不支持對角連接,如下圖
圖6
1和4位置的自動地圖元件並不連接起來,也就是說此時1位置並不會影響4位置的繪製情況。那什麼時候纔會影響呢?
看下圖
圖7
可以看到,當位置1和3同時存在時,位置0纔會影響到位置4。也就是說:當左邊和上邊都存在時,左上角纔有影響力。同理可得:當左邊不存在時,左上角和左下角都將失去影響力。(注意這點,後面就是靠這條理論來列舉情況的)
現在來列舉所有情況。可以不考慮對角,只考慮邊的組合。4邊的組合有16種,可以分爲5類:
1. 4邊都沒有(1種)
此時對角全失去影響力。位置4只有1種情況,即情況編號46
2. 只有1邊(4種)
此時對角依然全失去影響力。位置4只有4種情況,即情況42,43,44,45
3. 有2邊(6種)
3.1 對邊情況(2種)
即左邊和右邊同時出現,或者上邊和下邊同時出現,此時對角依然全失去影響力。位置4只有2種情況,即情況32,33
3.2 臨邊情況(4種)
考慮左邊和上邊同時出現,此時左上角有影響力,有左上角出現與不出現2種情況。其他臨邊情況一樣。位置4共有4*2=8種情況,即情況34-41
4. 有3邊(4種)
考慮左上右同時出現,此時左上角和右上角有影響力,2個角有4種出現情況。所以共有4*4=16種情況。即情況16-31
5. 4邊都有(1種)
此時所有角都有影響力,4個角有16種出現情況。所以共有1*16=16種情況。即情況0-15
上面的解析有點繞口,大家可以參考圖4理解一下。
所有情況加起來剛好47種。
分析好了所有情況,就可以列出每一種情況對應的哪4個小元件
下面把我辛苦寫出來的對應表給出:
等號左邊爲情況編號,右邊爲小元件表,注意小元件表裏面小元件的順序,是以
“左上,右上,左下,右下”的順序的。
[0] = [26,27,32,33]
[1] = [4,27,32,33]
[2] = [26,5,32,33]
[3] = [4,5,32,33]
[4] = [26,27,32,11]
[5] = [4,27,32,11]
[6] = [26,5,32,11]
[7] = [4,5,32,11]
[8] = [26,27,10,33]
[9] = [4,27,10,33]
[10] = [26,5,10,33]
[11] = [4,5,10,33]
[12] = [26,27,10,11]
[13] = [4,27,10,11]
[14] = [26,5,10,11]
[15] = [4,5,10,11]
[16] = [24,25,30,31]
[17] = [24,5,30,31]
[18] = [24,25,30,11]
[19] = [24,5,30,11]
[20] = [14,15,20,21]
[21] = [14,15,20,11]
[22] = [14,15,10,21]
[23] = [14,15,10,11]
[24] = [28,29,34,35]
[25] = [28,29,10,35]
[26] = [4,29,34,35]
[27] = [4,29,10,35]
[28] = [26,27,44,45]
[29] = [4,39,44,45]
[30] = [38,5,44,45]
[31] = [4,5,44,45]
[32] = [24,29,30,35]
[33] = [14,15,44,45]
[34] = [12,13,18,19]
[35] = [12,13,18,11]
[36] = [16,17,22,23]
[37] = [16,17,10,23]
[38] = [40,41,46,47]
[39] = [4,41,46,47]
[40] = [36,37,42,43]
[41] = [36,5,42,43]
[42] = [12,17,18,23]
[43] = [12,13,42,43]
[44] = [36,41,42,47]
[45] = [16,17,46,47]
[46] = [12,17,42,47]
[47] = [12,17,42,47]
3.繪製
繪製的過程大概是以下僞代碼
// 在row行,col列繪製一個自動地圖元件
void drawAt(int row, int col)
{
// 判斷row和col是否越界
// 將[row,col]位置填爲47號情況
// 這裏可以填任意情況,只要確保[row,col]位置不爲空即可
drawTileIndex(row,col,47);
// 更新[row,col]以及其周圍8個格子的狀態
updateTileState(row,col);
updateTileState(row-1,col-1);
updateTileState(row-1,col);
updateTileState(row-1,col+1);
updateTileState(row,col-1);
updateTileState(row,col+1);
updateTileState(row+1,col-1);
updateTileState(row+1,col);
updateTileState(row+1,col+1);
}
void drawTileIndex(int row, int col, int index)
{
// 根據index得到對應的小元件表
// 根據小元件表拼接成地圖元件
}
void updateTileState(int row, int col)
{
// 如果該位置沒有地圖元件,則直接返回
if (!hasTileAt(row,col)) return;
// 判斷其周圍8個格子的狀態state
// 根據判斷的狀態確定情況的編號index
drawTileIndex(row,col,index);
}
這裏最麻煩的就是“根據判斷的狀態確定情況的編號index”
由於情況有47種之多,而且判斷的過程也很糾結。這裏提供我的方法,雖然也很麻煩,不過邏輯比較清晰。
核心就是用二進制表達,邊角共有8個,用一個8位的char即可。首先定義如下:4邊4角各佔一位
left = 0000 0001
right = 0000 0010
top = 0000 0100
bottom = 0000 1000
left_top = 0001 0000
right_top = 0010 0000
left_bottom = 0100 0000
right_bottom = 1000 0000
那麼當前4邊4角的情況就可以上面的組合出來。
然後就可以用switch case表達47種情況,如
switch(state)
{
case 1111 1111: /*即4邊4角都有*/ index = 0; break;
case 1100 1000: /*即左邊和上邊以及左上角都有*/ index = 38; break;
...
}
4.小結
沒想到寫這樣一篇文章會這麼費力,在寫這傢伙時,我總感覺自己的方法太笨了。我感覺會有好的方法的,所以還是堅持寫下來,拋磚引玉了。