我雖然是90後,但是也很喜歡熱血傳奇2(以下簡稱“傳奇”)這款遊戲。
進入程式設計師行業後自己也對傳奇客戶端實現有所研究,現在將我的一些研究結果展示出來,如果大家有興趣的話不妨與我交流。
專案我託管到codeplex上了,使用GPLv2開源協議。大家可以checkout程式碼出來看。
我現在將地圖載入出來了,算是達到了里程碑1吧。
如果要將傳奇的地圖和資原始檔詳細解析可能我得寫上幾萬字,不過我現在越來越懶了,就只將讀取wix、wil、map檔案的方法和它們的解析貼出來吧。
準備工作:
JDK7
Eclipse
注意:
閱讀此篇文章後您將不需要再到網路上搜尋傳奇資原始檔和地圖檔案解析,因為我的隨筆絕對是最全最完整最詳細的!但這可能需要您花費一些耐心。
第一部分——地圖:
第一節——描述:
Q: Tile是什麼?
A: Tile在中文是“瓷磚”、“塊”的意思,具體到傳奇地圖中就是48*32螢幕畫素大小的矩形區域。單個傳奇地圖就是由多個Tile構成的。
Q: map格式檔案究竟存放了哪些資訊?
A: map格式檔案儲存了一個完成地圖的所有資訊,但是對於當前Tile的圖片只是儲存了一個索引而不是把圖片色彩資料儲存下來。
Q: map格式檔案怎樣讀取?
A: 對於檔案讀取以及對應到Java語言中的資料型別和資料結構我們要從兩方面考慮。
一是map的資料內容:
map檔案分為兩部分。一個檔案頭標識了當前地圖的高度、寬度等重要資訊;剩餘部分則是多個Tile的詳細資訊
二是map格式檔案是由Object-Pascal(以下簡稱Delphi)語言序列化而成的,我們首先需要了解從Delphi序列化的資料到Java反序列化需要進行的操作。
以上內容表明瞭地圖的資訊,熱血傳奇中地圖由Tile構成,每個Tile對應48*32螢幕畫素大小。
.map檔案則儲存了地圖的寬度、高度以及每個Tile的詳細資訊。
第二節——對應:
.map檔案如果對應到程式語言中資料結構的話在Delphi中如下(檔案頭):
1 TMapHeader = packed record 2 wWidth: Word; 3 wHeight :Word; 4 sTitle :String[16]; 5 UpdateDate :TDateTime; 6 Reserved :array[0..22] of Char;
(Tile,兩種都可以):
1 type 2 TMapInfo = packed record 3 wBkImg :Word; 4 wMidImg :Word; 5 wFrImg :Word; 6 btDoorIndex :Byte; 7 btDoorOffset :Byte; 8 btAniFrame :Byte; 9 btAniTick :Byte; 10 btArea :Byte; 11 btLight :Byte; 12 13 type 14 TMapInfo = packed record 15 wBigTileImg :Word; 16 wSmTileImg :Word; 17 wObjImg :Word; 18 btDoorIndex :Byte; 19 btDoorOffset :Byte; 20 btAniFrame :Byte; 21 btAniTick :Byte; 22 btObjFile :Byte; 23 btLight :Byte;
每個.map檔案如果在Delphi中就成了一個TMapHeader加wWidth*wHeight個MapTile。
(對於每個欄位佔用的位元組數請檢視下面Java程式碼中註釋)
由於我們是使用Java語言描述熱血傳奇地圖,所以我針對上述兩個資料結構使用Java語言進行了描述:
1 package org.coderecord.jmir.entt.internal; 2 3 import java.util.Date; 4 5 /** 6 * 熱血傳奇2地圖檔案頭 7 * <p> 8 * 針對*.map檔案的資料結構使用Java語言描述 9 * <br> 10 * 地圖檔案頭為52位元組,在Pascal中定義為 11 * <br> 12 * TMapHeader = packed record 13 * <br> 14 *  wWidth: Word; 15 * <br> 16 *  wHeight :Word; 17 * <br> 18 *  sTitle :String[16]; 19 * <br> 20 *  UpdateDate :TDateTime; 21 * <br> 22 *  Reserved :array[0..22] of Char; 23 * </p> 24 * <p> 25 * <b>wWidth</b> 表示地圖寬度(佔用兩個位元組,相當於Java語言short;一般不超過1000) 26 * <br> 27 * <b>wHeight</b> 表示地圖高度(佔用兩個位元組,相當於Java於洋short;一般不超過1000) 28 * <br> 29 * <b>sTitle</b> 標題,靜態單字串(佔用17個位元組,首位元組為字串已使用的長度即已存放的字元數,一般為“Legend of mir”) 30 * <br> 31 * <b>UpdateDate</b> 地圖最後更新時間(佔用8個位元組,為TDateTime型別,可使用{@link org.coderecord.jmir.kits.Pascal#readDate(byte[], int, boolean) readDate} 轉換為java.util.Date) 32 * <br> 33 * <b>Reserved</b> 保留字元,固定為23位元組 34 * </p> 35 * 36 * @author ShawRyan 37 * 38 */ 39 public class MapHeader { 40 41 /** 地圖寬度(橫向長度) */ 42 private short width; 43 /** 地圖高度(縱向長度) */ 44 private short height; 45 /** 標題 */ 46 private String title; 47 /** 更新日期 */ 48 private Date updateDate; 49 /** 保留字元 */ 50 private char[] reserved; 51 52 /** 預設建構函式 */ 53 public MapHeader() {} 54 /** 帶全部引數的建構函式 */ 55 public MapHeader(short width, short height, String title, Date updateDate, char[] reserved) { 56 this.width = width; 57 this.height = height; 58 this.title = title; 59 this.updateDate = updateDate; 60 this.reserved = reserved; 61 } 62 /** 使用已有物件構造例項 */ 63 public MapHeader(MapHeader mapHeader) { 64 this.width = mapHeader.getWidth(); 65 this.height = mapHeader.getHeight(); 66 this.title = mapHeader.getTitle(); 67 this.updateDate = mapHeader.getUpdateDate(); 68 this.reserved = mapHeader.getReserved(); 69 } 70 71 /** 獲取地圖寬度(橫向長度) */ 72 public short getWidth() { 73 return width; 74 } 75 /** 設定地圖寬度(橫向長度) */ 76 public void setWidth(short width) { 77 this.width = width; 78 } 79 /** 獲取地圖高度(縱向長度) */ 80 public short getHeight() { 81 return height; 82 } 83 /** 設定地圖高度(縱向長度) */ 84 public void setHeight(short height) { 85 this.height = height; 86 } 87 /** 獲取標題 */ 88 public String getTitle() { 89 return title; 90 } 91 /** 設定標題 */ 92 public void setTitle(String title) { 93 this.title = title; 94 } 95 /** 獲取更新時間 */ 96 public Date getUpdateDate() { 97 return updateDate; 98 } 99 /** 設定更新時間 */ 100 public void setUpdateDate(Date updateDate) { 101 this.updateDate = updateDate; 102 } 103 /** 獲取保留字元 */ 104 public char[] getReserved() { 105 return reserved; 106 } 107 /** 設定保留字元 */ 108 public void setReserved(char[] reserved) { 109 this.reserved = reserved; 110 } 111 }
(Tile我使用了兩種描述方式,後一種用於生產環境更加優秀):
1 package org.coderecord.jmir.entt.internal; 2 3 /** 4 * 熱血傳奇2地圖“塊” 5 * <br> 6 * 即 “<b>邏輯座標</b>”點(人物/NPC等放置需要佔用一個邏輯座標點) 7 * <br> 8 * 需要注意的是邏輯座標和螢幕座標是不一樣的,螢幕座標一般為畫素值,根據顯示器解析度設定而有所不同 9 * <br> 10 * 熱血傳奇2中一個邏輯座標點(地圖塊)需要佔用 48 * 32 螢幕座標大小 11 * <br> 12 * 每個地圖塊為2層結構,包括‘地’和‘空’ 13 * 例如樹葉投影下的地圖塊就是2層,包括地表及物體(如有突起石頭的地面或有水流的地面)和樹葉 14 * <p> 15 * 在Pascal語言中使用以下資料結構對地圖塊進行描述和儲存(兩種) 16 * <br> 17 * type 18 * <br> 19 * TMapInfo = packed record 20 * <br> 21 *  wBkImg :Word; 22 * <br> 23 *  wMidImg :Word; 24 * <br> 25 *  wFrImg :Word; 26 * <br> 27 *  btDoorIndex :Byte; 28 * <br> 29 *  btDoorOffset :Byte; 30 * <br> 31 *  btAniFrame :Byte; 32 * <br> 33 *  btAniTick :Byte; 34 * <br> 35 *  btArea :Byte; 36 * <br> 37 *  btLight :Byte; 38 * </p> 39 * <p> 40 * type 41 * <br> 42 * TMapInfo = packed record 43 * <br> 44 *  wBigTileImg :Word; 45 * <br> 46 *  wSmTileImg :Word; 47 * <br> 48 *  wObjImg :Word; 49 * <br> 50 *  btDoorIndex :Byte; 51 * <br> 52 *  btDoorOffset :Byte; 53 * <br> 54 *  btAniFrame :Byte; 55 * <br> 56 *  btAniTick :Byte; 57 * <br> 58 *  btObjFile :Byte; 59 * <br> 60 *  btLight :Byte; 61 * </p> 62 * <p> 63 * <b>wBkImg</b>或<b>wBigTileImg</b> 表示地圖地表圖片,如果最高位為1則表示不能通過(或站立),如河水型地表等。在判斷是否可以飛過(從空中通過)時則不需要考慮 64 * <br> 65 * <b>wMidImg</b>或<b>wSmTileImg</b> 表示地圖可視物體圖片(有時被稱為可視資料/中間層/小地圖塊/地圖補充背景等等),如果wBkImg(或wBigTileImg)沒有鋪滿則使用此地圖塊進行鋪墊。最高位不作為判斷依據,不過圖片索引一般小於0x8000,即最高位一般為0。例如在某地圖中第一個地圖塊的wBkImg(或wBigTileImg)大小為96 * 64,則代表該地圖左上角4個塊兒的地表都不為空,此時緊鄰的三個地圖塊都可以不用設定wBkImg(或wBigTileImg)和wMidImg(或wSmTileImg);如果某個地圖塊的沒有被其他塊兒的wBkImg(或wBigTileImg)鋪滿,自己也沒有wBkImg(或wBigTileImg),那麼它就需要一個wMidImg(或wSmTileImg)進行鋪墊。值得一提的是並不一定在有了wMidImg(或wBigTileImg)後就不需要繪製此層圖片了 66 * <br> 67 * <b>wFrImg</b>或<b>wObjImg</b> 表示表層圖片(物件),即空中遮擋物,如植物或建築物,如果最高位為1則表示不能通過(或站立)。在判斷是否可飛過(從空中通過)時需要作為唯一條件判斷,在判斷是否可以徒步通過或站立時需要聯合wBkImg進行判斷 68 * <br> 69 * <b>總的來說,地圖一般為兩層(只是針對上面的三個屬性,下方的也屬於地圖部分,不過先不納入考慮),包括背景層與物件層,背景層為wBkImg(或wBigTileImg)和wMidImg(或wSmTileImg)的集合,一般來說wBkImg就能搞定,也有時候需要兩者都有;Spirit(人物/怪物/NPC/掉落物品等)在兩層中間;索引從1開始,所以在從資源中真正取圖片時應該減1(適用於所有資源索引);索引一般最高位為0,為1一般表示特殊情況(在Java語言中可以理解為大於0,因為首位為1表示負數)</b> 70 * <br> 71 * <b>btDoorIndex</b> 門索引,最高位為1表示有門,為0表示沒有門。 72 * <br> 73 * <b>btDoorOffset</b> 門偏移,最高位為1表示門開啟了,為0表示門為關閉狀態 74 * <br> 75 * <b>btAniFrame</b> 幀數,指示當前地圖塊動態內容由多少張靜態圖片輪詢播放,需要和btAniTick一起起作用;如果最高位為1(即值大於0x80,或者在Java中為小於0的數值)則表示有動態內容 76 * <br> 77 * <b>btAniTick</b> 跳幀數,指示當前地圖塊動態內容應該每隔多少幀變換當前顯示的靜態圖片,需要和btAniFrame一起作用 78 * <br> 79 * <b>btAniFrame和btAniTick作用時表示式如下index = (gAniCount % (btAniFrame * (1 + btAniTick))) / (1 + btAniTick) 80 * <br> 81 *  其中gAniCount是當前畫面幀是第幾幀,它會在每次繪製遊戲介面時累加,它可以有最大值,超過可以置0;index是相對當前objImgIdx的偏移量,比如當前物件層圖片索引為1,而AniFrame為10,則表示從1到11這10副圖片應該作為一動態內容播放(有待考證) 82 * </b> 83 * <br> 84 * <b>btArea</b>或<b>btObjFile</b> 表示當前wFrImg(或wObjImg)和動態內容構成圖片來自哪個Object資原始檔,具體為Object{btArea}.wil中,如果btArea為0則是Objects.wil 85 * <br> 86 * <b>btLight</b> 亮度,一般為0/1/4 87 * </p> 88 * @author ShawRyan 89 * 90 */ 91 public class MapTile { 92 93 /** 背景圖索引 */ 94 private short bngImgIdx; 95 /** 補充背景圖索引 */ 96 private short midImgIdx; 97 /** 物件圖索引 */ 98 private short objImgIdx; 99 /** 門索引 */ 100 private byte doorIdx; 101 /** 門偏移 */ 102 private byte doorOffset; 103 /** 動畫幀數 */ 104 private byte aniFrame; 105 /** 動畫跳幀數 */ 106 private byte aniTick; 107 /** 資原始檔索引 */ 108 private byte objFileIdx; 109 /** 亮度 */ 110 private byte light; 111 112 /** 預設建構函式 */ 113 public MapTile() { } 114 /** 使用已有物件構造例項 */ 115 public MapTile(MapTile mapTile) { 116 this.bngImgIdx = mapTile.bngImgIdx; 117 this.midImgIdx = mapTile.midImgIdx; 118 this.objImgIdx = mapTile.objImgIdx; 119 this.doorIdx = mapTile.doorIdx; 120 this.doorOffset = mapTile.doorOffset; 121 this.aniFrame = mapTile.aniFrame; 122 this.aniTick = mapTile.aniTick; 123 this.objFileIdx = mapTile.objFileIdx; 124 this.light = mapTile.light; 125 } 126 /** 帶全部引數的建構函式 */ 127 public MapTile(short bngImgIdx, short midImgIdx, short objImgIdx, byte doorIdx, byte doorOffset, byte aniFrame, byte aniTick, byte objFileIdx, byte light) { 128 this.bngImgIdx = bngImgIdx; 129 this.midImgIdx = midImgIdx; 130 this.objImgIdx = objImgIdx; 131 this.doorIdx = doorIdx; 132 this.doorOffset = doorOffset; 133 this.aniFrame = aniFrame; 134 this.aniTick = aniTick; 135 this.objFileIdx = objFileIdx; 136 this.light = light; 137 } 138 139 /** 獲取背景圖索引 */ 140 public short getBngImgIdx() { 141 return bngImgIdx; 142 } 143 /** 設定背景圖索引 */ 144 public void setBngImgIdx(short bngImgIdx) { 145 this.bngImgIdx = bngImgIdx; 146 } 147 /** 獲取補充圖索引 */ 148 public short getMidImgIdx() { 149 return midImgIdx; 150 } 151 /** 設定補充圖索引 */ 152 public void setMidImgIdx(short midImgIdx) { 153 this.midImgIdx = midImgIdx; 154 } 155 /** 獲取物件圖索引 */ 156 public short getObjImgIdx() { 157 return objImgIdx; 158 } 159 /** 設定物件圖索引 */ 160 public void setObjImgIdx(short objImgIdx) { 161 this.objImgIdx = objImgIdx; 162 } 163 /** 獲取門索引 */ 164 public byte getDoorIdx() { 165 return doorIdx; 166 } 167 /** 設定門索引 */ 168 public void setDoorIdx(byte doorIdx) { 169 this.doorIdx = doorIdx; 170 } 171 /** 獲取門偏移 */ 172 public byte getDoorOffset() { 173 return doorOffset; 174 } 175 /** 設定門偏移 */ 176 public void setDoorOffset(byte doorOffset) { 177 this.doorOffset = doorOffset; 178 } 179 /** 獲取動畫幀數 */ 180 public byte getAniFrame() { 181 return aniFrame; 182 } 183 /** 設定動畫幀數 */ 184 public void setAniFrame(byte aniFrame) { 185 this.aniFrame = aniFrame; 186 } 187 /** 獲取動畫跳幀數 */ 188 public byte getAniTick() { 189 return aniTick; 190 } 191 /** 設定動畫跳幀數 */ 192 public void setAniTick(byte aniTick) { 193 this.aniTick = aniTick; 194 } 195 /** 獲取資原始檔索引 */ 196 public byte getObjFileIdx() { 197 return objFileIdx; 198 } 199 /** 設定資原始檔索引 */ 200 public void setObjFileIdx(byte objFileIdx) { 201 this.objFileIdx = objFileIdx; 202 } 203 /** 獲取亮度 */ 204 public byte getLight() { 205 return light; 206 } 207 /** 設定亮度 */ 208 public void setLight(byte light) { 209 this.light = light; 210 } 211 }
1 package org.coderecord.jmir.entt.internal; 2 3 import org.coderecord.jmir.scn.DrawSupport; 4 5 /** 6 * MapTile方便程式邏輯的另類解讀方式 7 * 8 * @author ShawRyan 9 * 10 */ 11 public class MapTileInfo { 12 /** 背景圖索引 */ 13 private short bngImgIdx; 14 /** 是否有背景圖(在熱血傳奇2地圖中,背景圖大小為4個地圖塊,具體到繪製地圖時則表現在只有橫縱座標都為雙數時才繪製),見{@link DrawSupport#drawMap(int, int, org.coderecord.jmir.entt.Map, int, int) drawMap} */ 15 private boolean hasBng; 16 /** 是否可行走(站立) */ 17 private boolean canWalk; 18 /** 補充背景圖索引 */ 19 private short midImgIdx; 20 /** 是否有補充圖 */ 21 private boolean hasMid; 22 /** 物件圖索引 */ 23 private short objImgIdx; 24 /** 是否有物件圖 */ 25 private boolean hasObj; 26 /** 是否可以飛越 */ 27 private boolean canFly; 28 /** 門索引 */ 29 private byte doorIdx; 30 /** 是否有門 */ 31 private boolean hasDoor; 32 /** 門偏移 */ 33 private byte doorOffset; 34 /** 門是否開啟 */ 35 private boolean doorOpen; 36 /** 動畫幀數 */ 37 private byte aniFrame; 38 /** 是否有動畫 */ 39 private boolean hasAni; 40 /** 動畫跳幀數 */ 41 private byte aniTick; 42 /** 資原始檔索引 */ 43 private byte objFileIdx; 44 /** 亮度 */ 45 private byte light; 46 47 /** 無參建構函式 */ 48 public MapTileInfo() { } 49 /** 帶全部引數的建構函式 */ 50 public MapTileInfo(short bngImgIdx, boolean hasBng, boolean canWalk, short midImgIdx, boolean hasMid, short objImgIdx, boolean hasObj, boolean canFly, byte doorIdx, boolean hasDoor, byte doorOffset, boolean doorOpen, byte aniFrame, boolean hasAni, byte aniTick, byte objFileIdx, byte light) { 51 this.bngImgIdx = bngImgIdx; 52 this.hasBng = hasBng; 53 this.canWalk = canWalk; 54 this.midImgIdx = midImgIdx; 55 this.hasMid = hasMid; 56 this.objImgIdx = objImgIdx; 57 this.hasObj = hasObj; 58 this.canFly = canFly; 59 this.doorIdx = doorIdx; 60 this.hasDoor = hasDoor; 61 this.doorOffset = doorOffset; 62 this.doorOpen = doorOpen; 63 this.aniFrame = aniFrame; 64 this.hasAni = hasAni; 65 this.aniTick = aniTick; 66 this.objFileIdx = objFileIdx; 67 this.light = light; 68 } 69 /** 基於已有實體構造物件 */ 70 public MapTileInfo(MapTileInfo mapTileInfo) { 71 this.bngImgIdx = mapTileInfo.bngImgIdx; 72 this.hasBng = mapTileInfo.hasBng; 73 this.canWalk = mapTileInfo.canWalk; 74 this.midImgIdx = mapTileInfo.midImgIdx; 75 this.hasMid = mapTileInfo.hasMid; 76 this.objImgIdx = mapTileInfo.objImgIdx; 77 this.hasObj = mapTileInfo.hasObj; 78 this.canFly = mapTileInfo.canFly; 79 this.doorIdx = mapTileInfo.doorIdx; 80 this.hasDoor = mapTileInfo.hasDoor; 81 this.doorOffset = mapTileInfo.doorOffset; 82 this.doorOpen = mapTileInfo.doorOpen; 83 this.aniFrame = mapTileInfo.aniFrame; 84 this.hasAni = mapTileInfo.hasAni; 85 this.aniTick = mapTileInfo.aniTick; 86 this.objFileIdx = mapTileInfo.objFileIdx; 87 this.light = mapTileInfo.light; 88 } 89 90 /** 獲取背景圖索引 */ 91 public short getBngImgIdx() { 92 return bngImgIdx; 93 } 94 /** 設定背景圖索引 */ 95 public void setBngImgIdx(short bngImgIdx) { 96 this.bngImgIdx = bngImgIdx; 97 } 98 /** 獲取該地圖塊是否有背景圖 */ 99 public boolean isHasBng() { 100 return hasBng; 101 } 102 /** 設定該地圖塊是否有背景圖 */ 103 public void setHasBng(boolean hasBng) { 104 this.hasBng = hasBng; 105 } 106 /** 獲取該地圖塊是否可以站立或走過 */ 107 public boolean isCanWalk() { 108 return canWalk; 109 } 110 /** 設定該地圖塊是否可以站立或走過 */ 111 public void setCanWalk(boolean canWalk) { 112 this.canWalk = canWalk; 113 } 114 /** 獲取補充圖索引 */ 115 public short getMidImgIdx() { 116 return midImgIdx; 117 } 118 /** 設定補充圖索引 */ 119 public void setMidImgIdx(short midImgIdx) { 120 this.midImgIdx = midImgIdx; 121 } 122 /** 獲取該地圖塊是否有補充圖 */ 123 public boolean isHasMid() { 124 return hasMid; 125 } 126 /** 設定該地圖塊是否有補充圖 */ 127 public void setHasMid(boolean hasMid) { 128 this.hasMid = hasMid; 129 } 130 /** 獲取物件圖索引 */ 131 public short getObjImgIdx() { 132 return objImgIdx; 133 } 134 /** 設定物件圖索引 */ 135 public void setObjImgIdx(short objImgIdx) { 136 this.objImgIdx = objImgIdx; 137 } 138 /** 獲取該地圖塊是否有物件圖 */ 139 public boolean isHasObj() { 140 return hasObj; 141 } 142 /** 設定該地圖塊是否有物件圖 */ 143 public void setHasObj(boolean hasObj) { 144 this.hasObj = hasObj; 145 } 146 /** 獲取該地圖塊是否可以飛越 */ 147 public boolean isCanFly() { 148 return canFly; 149 } 150 /** 設定該地圖塊是否可以飛越 */ 151 public void setCanFly(boolean canFly) { 152 this.canFly = canFly; 153 } 154 /** 獲取門索引 */ 155 public byte getDoorIdx() { 156 return doorIdx; 157 } 158 /** 設定門索引 */ 159 public void setDoorIdx(byte doorIdx) { 160 this.doorIdx = doorIdx; 161 } 162 /** 獲取該地圖塊是否有門 */ 163 public boolean isHasDoor() { 164 return hasDoor; 165 } 166 /** 設定該地圖塊是否有門 */ 167 public void setHasDoor(boolean hasDoor) { 168 this.hasDoor = hasDoor; 169 } 170 /** 獲取門偏移 */ 171 public byte getDoorOffset() { 172 return doorOffset; 173 } 174 /** 設定門偏移 */ 175 public void setDoorOffset(byte doorOffset) { 176 this.doorOffset = doorOffset; 177 } 178 /** 獲取該地圖塊門是否開啟 */ 179 public boolean isDoorOpen() { 180 return doorOpen; 181 } 182 /** 設定該地圖塊門是否開啟 */ 183 public void setDoorOpen(boolean doorOpen) { 184 this.doorOpen = doorOpen; 185 } 186 /** 獲取動畫幀數 */ 187 public byte getAniFrame() { 188 return aniFrame; 189 } 190 /** 設定動畫幀數 */ 191 public void setAniFrame(byte aniFrame) { 192 this.aniFrame = aniFrame; 193 } 194 /** 獲取該地圖塊是否有動畫 */ 195 public boolean isHasAni() { 196 return hasAni; 197 } 198 /** 設定該地圖塊是否有動畫 */ 199 public void setHasAni(boolean hasAni) { 200 this.hasAni = hasAni; 201 } 202 /** 獲取動畫跳幀數 */ 203 public byte getAniTick() { 204 return aniTick; 205 } 206 /** 設定動畫跳幀數 */ 207 public void setAniTick(byte aniTick) { 208 this.aniTick = aniTick; 209 } 210 /** 獲取資原始檔索引 */ 211 public byte getObjFileIdx() { 212 return objFileIdx; 213 } 214 /** 設定資原始檔索引 */ 215 public void setObjFileIdx(byte objFileIdx) { 216 this.objFileIdx = objFileIdx; 217 } 218 /** 獲取亮度 */ 219 public byte getLight() { 220 return light; 221 } 222 /** 設定亮度 */ 223 public void setLight(byte light) { 224 this.light = light; 225 } 226 }
對於讀取物理檔案到產生物件,我使用一些工具方法,這裡主要是高低位的問題,還有就是Delphi中字串和時間日期到Java的不同處理。
第三節——讀取:
我對.map檔案讀取資料生成物件的過程如下(工具方法請查閱原始碼,就不在此貼出了):
1 /** 2 * 通過位元組陣列反序列化地圖檔案頭資料 3 * 4 * @param bytes 5 * 資料(檔案中直接讀取,未經過任何處理的位元組陣列) 6 * @return 7 * 地圖檔案頭資訊 8 */ 9 public static MapHeader readMapHeader(byte[] bytes) { 10 MapHeader res = new MapHeader(); 11 res.setWidth(Common.readShort(bytes, 0, true)); 12 res.setHeight(Common.readShort(bytes, 2, true)); 13 res.setTitle(readStaticSingleString(bytes, 4)); 14 res.setUpdateDate(readDate(bytes, 21, true)); 15 res.setReserved(readChars(bytes, 29, 23)); 16 return res; 17 }
1 /** 2 * 通過位元組陣列反序列化地圖邏輯座標塊兒資料 3 * 4 * @param bytes 5 * 資料(檔案中直接讀取,未經過任何處理的位元組陣列) 6 * @return 7 * 地圖邏輯座標塊兒資訊 8 */ 9 public static MapTile readMapTile(byte[] bytes) { 10 MapTile res = new MapTile(); 11 res.setBngImgIdx(Common.readShort(bytes, 0, true)); 12 res.setMidImgIdx(Common.readShort(bytes, 2, true)); 13 res.setObjImgIdx(Common.readShort(bytes, 4, true)); 14 res.setDoorIdx(bytes[6]); 15 res.setDoorOffset(bytes[7]); 16 res.setAniFrame(bytes[8]); 17 res.setAniTick(bytes[9]); 18 res.setObjFileIdx(bytes[10]); 19 res.setLight(bytes[11]); 20 return res; 21 } 22 23 /** 24 * 通過位元組陣列反序列化地圖邏輯座標塊資訊 25 * 26 * @param bytes 27 * 資料(檔案中直接讀取,未經過任何處理的位元組陣列) 28 * @return 29 * 地圖邏輯座標塊兒資訊 30 */ 31 public static MapTileInfo readMapTileInfo(byte[] bytes) { 32 MapTileInfo res = new MapTileInfo(); 33 // 讀取背景 34 short bng = Common.readShort(bytes, 0, true); 35 // 讀取中間層 36 short mid = Common.readShort(bytes, 2, true); 37 // 讀取物件層 38 short obj = Common.readShort(bytes, 4, true); 39 // 設定背景 40 if((bng & 0b0111_1111_1111_1111) > 0) { 41 res.setBngImgIdx((short) (bng & 0b0111_1111_1111_1111)); 42 res.setHasBng(true); 43 } 44 // 設定中間層 45 if((mid & 0b0111_1111_1111_1111) > 0) { 46 res.setMidImgIdx((short) (mid & 0b0111_1111_1111_1111)); 47 res.setHasMid(true); 48 } 49 // 設定物件層 50 if((obj & 0b0111_1111_1111_1111) > 0) { 51 res.setObjImgIdx((short) (obj & 0b0111_1111_1111_1111)); 52 res.setHasObj(true); 53 } 54 // 設定是否可站立 55 res.setCanWalk(!Common.is1AtTopDigit(bng) && !Common.is1AtTopDigit(obj)); 56 // 設定是否可飛行 57 res.setCanFly(!Common.is1AtTopDigit(obj)); 58 59 res.setDoorOffset(bytes[7]); 60 if(Common.is1AtTopDigit(bytes[7])) res.setDoorOpen(true); 61 res.setAniFrame(bytes[8]); 62 if(Common.is1AtTopDigit(bytes[8])) { 63 res.setAniFrame((byte) (bytes[8] & 0x7F)); 64 res.setHasAni(true); 65 } 66 res.setAniTick(bytes[9]); 67 res.setObjFileIdx(bytes[10]); 68 res.setLight(bytes[11]); 69 return res; 70 }
第二部分——資原始檔:
第一節——描述:
Q: wix和wil檔案分別是什麼?
A: wix全名為“Wemade Image Index”,顧名思義是圖片庫索引;wil全名為“Wemade Image Lib”,意為圖片庫。wix檔案中儲存了對應圖片庫的基本資訊,包括圖片數量、每個圖片色彩資料起始位置;wil檔案則儲存了包括圖片調色盤、圖片色彩資料在內的所有圖片資訊。
Q: wix和wil需要對應起來用嗎?
A: 其實在生產環境中只需要使用wil就可以了,它包含了程式所需所有資訊,網路上有人說必須使用wix來尋找每個圖片色彩資料起始位置的說法是錯誤的,不過結合起來使用能最大限度避免錯誤,如果不服,請聯絡我!
Q: wix檔案結構和wil檔案結構?
A: wix檔案由標題、圖片數和圖片色彩資料起始位置(對應wil中索引)的陣列構成;wil檔案由檔案頭和多個圖片資料構成,檔案頭內容相對固定,每個圖片色彩資料長度都不盡相同。
第二節——對應:
wix檔案:
Delphi語言描述(起始位置陣列沒有加上):
1 type 2 TIndexHeader = record 3 sTitle :String[40]; 4 iIndexCount :Integer;
Java語言描述:
1 package org.coderecord.jmir.entt; 2 3 /** 4 * WIX即“WEMADE IMAGE INDEX” 5 * <br> 6 * 意味圖片索引 7 * <br> 8 * 在熱血傳奇2中,圖片資源儲存方式一般為一個wix檔案和一個wil檔案形成一組,負責儲存一個內容或功能的圖片資源 9 * <br> 10 * wix檔案為索引檔案,通過它從wil檔案中解析出圖片資料 11 * <br> 12 * 對於wix檔案(頭)可使用如下資料結構進行描述 13 * <br> 14 * type 15 * <br> 16 * TIndexHeader = record 17 * <br> 18 *  sTitle :String[40]; 19 * <br> 20 *  iIndexCount :Integer; 21 * <br> 22 * <b>sTitle一般為“INDX v1.0-WEMADE Entertainment inc.”,表示標題;iIndexCount表示對應wil(lib庫檔案)中儲存的圖片數量;其實重點是此檔案中前44個位元組用處不大,從45位元組開始才是我們關心的。</b> 23 * <p> 24 * <b>注意:</b>為什麼sTitle是String[40]而非String[43],如果是後者,則sTitle會佔用44位元組,剛好是我們所說的前44位元組?其實Pascal中對record的packed修飾符有特殊處理,<b>不帶</b>packed表示“對齊”(對齊意味著能被4整除),現在sTitle為String[40],加上其頭部修飾的一個位元組佔共用41位元組,並不能被4整除,所以編譯器會在後面加上3個位元組來<b>“對齊”</b>,所以在這裡sTitle一共佔用44位元組 25 * </p> 26 * 從第49位元組開始則是iIndexCount個Integer型別資料,標識在對應wil檔案中各個圖片資料對應位置(array of Integer),這裡的位置索引從0開始,指示在檔案二進位制資料的索引 27 * 28 * @author ShawRyan 29 * 30 */ 31 public class WIX { 32 /** 標題 */ 33 private String title; 34 /** 資源數量 */ 35 private int indexCount; 36 /** 資源資料起始位置 */ 37 private int[] indexArray; 38 39 /** 預設建構函式 */ 40 public WIX() {} 41 /** 基於已有物件構造例項 */ 42 public WIX(WIX wix) { 43 this.title = wix.title; 44 this.indexCount = wix.indexCount; 45 this.indexArray = wix.indexArray; 46 } 47 /** 使用全部引數構造例項 */ 48 public WIX(String title, int indexCount, int[] indexArray) { 49 this.title = title; 50 this.indexCount = indexCount; 51 this.indexArray = indexArray; 52 } 53 54 /** 獲取標題 */ 55 public String getTitle() { 56 return title; 57 } 58 /** 設定標題 */ 59 public void setTitle(String title) { 60 this.title = title; 61 } 62 /** 獲取資源數量 */ 63 public int getIndexCount() { 64 return indexCount; 65 } 66 /** 設定資源數量 */ 67 public void setIndexCount(int indexCount) { 68 this.indexCount = indexCount; 69 } 70 /** 獲取資源索引 */ 71 public int[] getIndexArray() { 72 return indexArray; 73 } 74 /** 設定資源索引 */ 75 public void setIndexArray(int[] indexArray) { 76 this.indexArray = indexArray; 77 } 78 }
wil檔案:
Delphi語言描述(調色盤未加上,調色盤說起來比較麻煩):
1 type 2 TLibHeader = record 3 sTitle :String[40]; 4 iImageCount :Integer; 5 iColorCount :Integer; 6 iPaletteSize :Integer;
1 type 2 TImageInfo = record 3 siWidth :SmallInt; 4 siHeight :SmallInt; 5 siPx :SmallInt; 6 siPy :SmallInt; 7 Bits :PByte;
Java語言描述:
1 package org.coderecord.jmir.entt; 2 3 import org.coderecord.jmir.entt.internal.ImageInfo; 4 import org.coderecord.jmir.entt.internal.LibHeader; 5 6 /** 7 * WIL即“WEMADE IMAGE LIBRARY” 8 * <br> 9 * 意為圖片庫 10 * <br> 11 * 存放多個圖片資料(包括畫素點色彩) 12 * <br> 13 * WIL檔案由頭部和多個圖片資訊組成 14 * <br> 15 * 頭部可以使用如下型別進行描述 16 * <br> 17 * type 18 * <br> 19 * TLibHeader = record 20 * <br> 21 *  sTitle :String[40]; 22 * <br> 23 *  iImageCount :Integer; 24 * <br> 25 *  iColorCount :Integer; 26 * <br> 27 *  iPaletteSize :Integer; 28 * <br> 29 * <b>頭部共佔用56位元組,sTitle佔用44位元組,原因參見{@link WIX}。其中sTitle為標題,一般為“ILIB v1.0-WEMADE Entertainment inc.”;iImageCount為圖片數量;iColorCount表示色彩位深度,如256表示8bit點陣圖,65536表示16bit點陣圖(2的冪數);iPaletteSize表示調色盤位元組數</b> 30 * <br> 31 * 頭部後則是調色盤(調色盤請參照{@link org.coderecord.jmir.entt.internal.LibHeader#pallette LibHeader})和多個圖片資料,包括圖片寬高/座標和畫素色彩,可以使用下面的結構進行描述(不包括調色盤,調色盤其實也可放入header中,型別為 array of Byte) 32 * <br> 33 * type 34 * <br> 35 * TImageInfo = record 36 * <br> 37 *  siWidth :SmallInt; 38 * <br> 39 *  siHeight :SmallInt; 40 * <br> 41 *  siPx :SmallInt; 42 * <br> 43 *  siPy :SmallInt; 44 * <br> 45 *  Bits :PByte; 46 * <br> 47 * <b>siWidth</b> 圖片寬度 48 * <br> 49 * <b>siHeight</b> 圖片高度 50 * <br> 51 * <b>siPx</b> 圖片橫向偏移量 52 * <br> 53 * <b>siPy</b> 圖片縱向偏移量 54 * <br> 55 * <b>Bits</b> 圖片畫素色彩值陣列 56 * 57 * @author ShawRyan 58 * 59 */ 60 public class WIL { 61 62 /** 檔案頭 */ 63 private LibHeader header; 64 /** 圖片陣列 */ 65 private ImageInfo[] images; 66 67 /** 無參建構函式 */ 68 public WIL() {} 69 /** 基於已有例項構造物件 */ 70 public WIL(WIL wil) { 71 this.header = wil.header; 72 this.images = wil.images; 73 } 74 /** 全參建構函式 */ 75 public WIL(LibHeader header, ImageInfo[] images) { 76 this.header = header; 77 this.images = images; 78 } 79 80 /** 獲取頭部 */ 81 public LibHeader getHeader() { 82 return header; 83 } 84 /** 設定頭部 */ 85 public void setHeader(LibHeader header) { 86 this.header = header; 87 } 88 /** 獲取圖片 */ 89 public ImageInfo[] getImages() { 90 return images; 91 } 92 /** 設定圖片 */ 93 public void setImages(ImageInfo[] images) { 94 this.images = images; 95 } 96 }
1 package org.coderecord.jmir.entt.internal; 2 3 /** WIL檔案頭 */ 4 public class LibHeader { 5 /** 長度(位元組數),不包括調色盤大小 */ 6 public static final int BIT_LENGTH_WITHOUT_PATTELE = 56; 7 8 /** 標題 */ 9 private String title; 10 /** 圖片數量 */ 11 private int imageCount; 12 /** 色深度 */ 13 private int colorCount; 14 /** 調色盤位元組數 */ 15 private int paletteSize; 16 /** 17 * 調色盤 18 * <br> 19 * 調色盤使用是一組位元組陣列即二維位元組陣列,第二維總為4位元組,依次儲存藍、綠、紅、Alpha色彩值 20 * <br> 21 * <br> 22 * 對於任意色彩而言,都應該使用24位來儲存,比如FF0000表示紅色 23 * <br> 24 * 24位色彩值也被稱為真彩 25 * <br> 26 * Windows 中點陣圖有兩種格式: 27 * <br> 28 *  裝置相關點陣圖 Device Depend Bitmap DDB 29 * <br> 30 *  裝置無關點陣圖 Device Independ Bitmap DIB 31 * <br> 32 * 熱血傳奇使用DIB對圖片進行儲存 33 * <br> 34 * 色彩深度有時少於24位,有時是因為精度要求並不會那麼高,例如使用5或6個位元組儲存的R/G/B值就已經夠用,此時可使用16位顏色值儲存一個畫素點的顏色 35 * <br> 36 *  有時甚至一幅圖的色彩不超過256種,此時就可以將這256中顏色提取出來作為一個調色盤,然後畫素點色彩值則儲存色彩值在這個調色盤中的索引,這就是8位顏色值 37 * <br> 38 *  對於單色或16色(即使用1bit或4bit儲存色彩值的情況不與考慮) 39 */ 40 private int[] palette; 41 42 /** 無參建構函式 */ 43 public LibHeader() {} 44 /** 基於已有物件構造例項 */ 45 public LibHeader(LibHeader header) { 46 this.title = header.title; 47 this.imageCount = header.imageCount; 48 this.colorCount = header.colorCount; 49 this.paletteSize = header.paletteSize; 50 this.palette = header.palette; 51 } 52 /** 帶全部引數的建構函式 */ 53 public LibHeader(String title, int imageCount, int colorCount, int paletteSize, int[] pallette) { 54 this.title = title; 55 this.imageCount = imageCount; 56 this.colorCount = colorCount; 57 this.paletteSize = paletteSize; 58 this.palette = pallette; 59 } 60 61 /** 獲取標題 */ 62 public String getTitle() { 63 return title; 64 } 65 /** 設定標題 */ 66 public void setTitle(String title) { 67 this.title = title; 68 } 69 /** 獲取圖片數量 */ 70 public int getImageCount() { 71 return imageCount; 72 } 73 /** 設定圖片數量 */ 74 public void setImageCount(int imageCount) { 75 this.imageCount = imageCount; 76 } 77 /** 獲取色深 */ 78 public int getColorCount() { 79 return colorCount; 80 } 81 /** 設定色深 */ 82 public void setColorCount(int colorCount) { 83 this.colorCount = colorCount; 84 } 85 /** 獲取調色盤大小 */ 86 public int getPaletteSize() { 87 return paletteSize; 88 } 89 /** 設定調色盤大小 */ 90 public void setPaletteSize(int paletteSize) { 91 this.paletteSize = paletteSize; 92 } 93 /** 獲取調色盤 */ 94 public int[] getPalette() { 95 return palette; 96 } 97 /** 設定調色盤 */ 98 public void setPalette(int[] pallette) { 99 this.palette = pallette; 100 } 101 }
1 package org.coderecord.jmir.entt.internal; 2 3 /** 圖片資訊 */ 4 public class ImageInfo { 5 /** 圖片寬度 */ 6 private short width; 7 /** 圖片高度 */ 8 private short height; 9 /** 圖片橫向偏移量 */ 10 private short offsetX; 11 /** 圖片縱向偏移量 */ 12 private short offsetY; 13 /** 14 * 圖片資料 15 * <br> 16 * 逐列儲存畫素色彩值,對於4 * 4的圖片而言,其色彩資料如下(以位元組為單位) 17 * <br> 18 * <b>注意:</b>如果圖片寬度位元組數不是4的倍數會有填充位元組數,如果是讀取真正的圖片資料可以不用考慮,但如果需要一次性讀取多張圖片則需要跳過填充的位元組,參見{@link org.coderecord.jmir.kits.Pascal#fillPByteLineWidth(int, int) fillPByteLineWidth} 19 * <br> 20 * 8位 21 * <br> 22 * 12 8 4 0  23 * <br> 24 * 13 9 5 1  25 * <br> 26 * 14 10 6 2  27 * <br> 28 * 15 11 7 3  29 * <br> 30 * 16位 31 * <br> 32 * 24&25 16&17 8&9 0&1  33 * <br> 34 * 26&27 18&19 10&11 2&3  35 * <br> 36 * 28&29 20&21 12&13 4&5  37 * <br> 38 * 30&31 22&23 14&15 6&7  39 */ 40 private byte[] pixels; 41 42 /** 無參建構函式 */ 43 public ImageInfo() {} 44 /** 基於已有物件構造例項 */ 45 public ImageInfo(ImageInfo imageInfo) { 46 this.height = imageInfo.height; 47 this.offsetX = imageInfo.offsetX; 48 this.offsetY = imageInfo.offsetY; 49 this.pixels = imageInfo.pixels; 50 this.width = imageInfo.width; 51 } 52 /** 帶全部引數的建構函式 */ 53 public ImageInfo(short width, short height, short offsetX, short offsetY, byte[] pixels) { 54 this.width = width; 55 this.height = height; 56 this.offsetX = offsetX; 57 this.offsetY = offsetY; 58 this.pixels = pixels; 59 } 60 61 /** 獲取圖片寬度 */ 62 public short getWidth() { 63 return width; 64 } 65 /** 設定圖片高度 */ 66 public void setWidth(short width) { 67 this.width = width; 68 } 69 /** 獲取圖片高度 */ 70 public short getHeight() { 71 return height; 72 } 73 /** 設定圖片高度 */ 74 public void setHeight(short height) { 75 this.height = height; 76 } 77 /** 獲取圖片橫線偏移量 */ 78 public short getOffsetX() { 79 return offsetX; 80 } 81 /** 設定圖片橫向偏移量 */ 82 public void setOffsetX(short offsetX) { 83 this.offsetX = offsetX; 84 } 85 /** 獲取圖片縱向偏移量 */ 86 public short getOffsetY() { 87 return offsetY; 88 } 89 /** 設定圖片縱向偏移量 */ 90 public void setOffsetY(short offsetY) { 91 this.offsetY = offsetY; 92 } 93 /** 獲取圖片二進位制資料 */ 94 public byte[] getPixels() { 95 return pixels; 96 } 97 /** 設定圖片二進位制資料 */ 98 public void setPixels(byte[] pixels) { 99 this.pixels = pixels; 100 } 101 }
第三節——讀取:
我們的目的在於使用wil中的圖片,在上圖其實可以看到我們只差一個每個圖片色彩資料大小(??處),這個大小可以自己計算得到(涉及到Delphi點陣圖處理,資訊量較大,不在此贅述),但我們也可以從wix中拿到,這樣比較方便,而且不會出錯。
1 /** 2 * 根據庫檔案路徑和索引檔案路徑讀取圖片庫 3 * 4 * @param wilPath 5 * 圖片庫檔案路徑 6 * @param wixPath 7 * 圖片索引檔案路徑 8 * @return 9 * 得到的圖片庫檔案路徑 10 */ 11 public static WIL readWILFromFile(String wilPath, String wixPath) { 12 WIX wix = new WIX(); 13 try(FileInputStream fis = new FileInputStream(wixPath)) { 14 wix.setTitle(Pascal.readStaticSingleString(fis, 0, 44)); 15 wix.setIndexCount(Common.readInt(fis, 0, true)); 16 int[] imageIndexs = new int[wix.getIndexCount()]; 17 for(int i = 0; i < imageIndexs.length; ++i) 18 imageIndexs[i] = Common.readInt(fis, 0, true); 19 wix.setIndexArray(imageIndexs); 20 } catch (FileNotFoundException e) { 21 throw new RuntimeException(e); 22 } catch (IOException e) { 23 throw new RuntimeException(e); 24 } 25 WIL res = new WIL(); 26 try(FileInputStream fis = new FileInputStream(wilPath)) { 27 res.setHeader(Pascal.readLibHeader(fis)); 28 } catch (FileNotFoundException e) { 29 throw new RuntimeException(e); 30 } catch (IOException e) { 31 throw new RuntimeException(e); 32 } 33 try(RandomAccessFile raf = new RandomAccessFile(wilPath, "r")){ 34 ImageInfo[] images = new ImageInfo[res.getHeader().getImageCount()]; 35 for(int i = 0; i < images.length; ++i) 36 images[i] = Pascal.readImageInfo(raf, wix.getIndexArray()[i], Pascal.colorCountToBitCount(res.getHeader().getColorCount())); 37 res.setImages(images); 38 } catch (FileNotFoundException e) { 39 throw new RuntimeException(e); 40 } catch (IOException e) { 41 throw new RuntimeException(e); 42 } 43 return res; 44 }
第四節——圖片處理:
圖片資料需要經過轉換才能在介面上展示(針對8位的圖片):
首先在讀取LibHeader時就需要做透明色處理:
1 /** 2 * 從流中讀取圖片庫檔案頭 3 * 4 * @param is 5 * 資料流 6 * @return 7 * 檔案頭 8 * @throws IOException 9 * 可能的流異常 10 */ 11 public static LibHeader readLibHeader(InputStream is) throws IOException { 12 LibHeader res = new LibHeader(); 13 res.setTitle(Pascal.readStaticSingleString(is, 0, 44)); 14 res.setImageCount(Common.readInt(is, 0, true)); 15 res.setColorCount(Common.readInt(is, 0, true)); 16 res.setPaletteSize(Common.readInt(is, 0, true)); 17 int[] palette = new int[res.getPaletteSize() / 4]; 18 for(int i = 0; i < palette.length; ++i) { 19 byte[] byteArgb = new byte[4]; 20 is.read(byteArgb); 21 // 最重要的一步,重設Alpha值 22 // 調色盤的Alpha值都為0,而實際上只有0x00000000(一般在調色盤第一個色彩)才是透明色,即熱血傳奇2圖片庫中8位色彩沒有不透明的純黑色 23 if(byteArgb[2] == 0 && byteArgb[1] == 0 && byteArgb[0] == 0) 24 byteArgb[3] = 0; 25 else 26 byteArgb[3] = (byte) 255; 27 28 palette[i] = Common.readInt(byteArgb, 0, true); 29 } 30 31 res.setPalette(palette); 32 return res; 33 }
在顯示時要記住圖片顏色資料的儲存是從右向左,從上往下的:
1 /** 2 * 從ImageInfo物件及調色盤和色彩深度讀取圖片資料 3 * 4 * @param imageInfo 5 * 圖片原資料資訊 6 * @param palette 7 * 調色盤 8 * @param bitCount 9 * 色彩深度 10 * @return 11 */ 12 public static BufferedImage readImage(ImageInfo imageInfo, int[] palette, int bitCount) { 13 BufferedImage res = null; 14 if(bitCount == 8) { 15 res = new BufferedImage(imageInfo.getWidth(), imageInfo.getHeight(), BufferedImage.TYPE_INT_ARGB); 16 17 int index = 0; 18 for(int h = 0; h < imageInfo.getHeight(); ++h) 19 for(int w = 0; w < imageInfo.getWidth(); ++w) 20 res.setRGB(w, res.getHeight() - 1 - h, palette[imageInfo.getPixels()[index++] & 0xff]); 21 } else if(bitCount == 16) { 22 // FIXME 23 res = new BufferedImage(imageInfo.getWidth(), imageInfo.getHeight(), BufferedImage.TYPE_USHORT_565_RGB); 24 int index = 0; 25 for(int h = 0; h < imageInfo.getHeight(); ++h) 26 for(int w = 0; w < imageInfo.getWidth(); ++w, index += 2) 27 res.setRGB(w, res.getHeight() - 1 - h, Common.readShort(imageInfo.getPixels(), index, true)); 28 } else if(bitCount == 24) { 29 // FIXME 30 //res = new BufferedImage(imageInfo.getWidth(), imageInfo.getHeight(), BufferedImage.TYPE_INT_RGB); 31 //int index = 0; 32 //for(int h = 0; h < imageInfo.getHeight(); ++h) 33 // for(int w = 0; w < imageInfo.getWidth(); ++w) 34 // res.setRGB(w, h, Common.readInt(imageInfo.getPixels(), index++, true)); 35 } else { 36 // FIXME 37 //res = new BufferedImage(imageInfo.getWidth(), imageInfo.getHeight(), BufferedImage.TYPE_INT_RGB); 38 //int index = 0; 39 //for(int h = 0; h < imageInfo.getHeight(); ++h) 40 // for(int w = 0; w < imageInfo.getWidth(); ++w) 41 // res.setRGB(w, h, Common.readInt(imageInfo.getPixels(), index++, true)); 42 } 43 return res; 44 }
說了這麼久,我自己都糊塗了。大家如果不清楚請下載原始碼或基於Eclipse和JDK的專案進行檢視。
編輯於2015-01-25 21:06:33
專案沒有繼續下去,不過我用lwjgl重寫了部分,實現了地圖載入和紋理讀取。大家可以去新的專案地址checkout程式碼。
編輯於2017-08-16 13:53:40
github上我上傳了一個輕便的庫,可以一看https://github.com/jootnet/mir2.core
(最後編輯時間2014-03-16 15:43:23)