我雖然是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)