淺談RPG Maker XP自動地圖元件的繪製原理

淺談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

14位置的自動地圖元件並不連接起來,也就是說此時1位置並不會影響4位置的繪製情況。那什麼時候纔會影響呢?

看下圖

7

可以看到,當位置13同時存在時,位置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即可。首先定義如下:44角各佔一位

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

那麼當前44角的情況就可以上面的組合出來。

然後就可以用switch case表達47種情況,如

switch(state)
{
case 1111 1111: /*即4邊4角都有*/ index = 0; break;
case 1100 1000: /*即左邊和上邊以及左上角都有*/ index = 38; break;
...
}

4.小結

沒想到寫這樣一篇文章會這麼費力,在寫這傢伙時,我總感覺自己的方法太笨了。我感覺會有好的方法的,所以還是堅持寫下來,拋磚引玉了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章