JMir——Java版熱血傳奇2之資源文件與地圖


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

      每個.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  * &nbsp;TMapHeader = packed record
 13  * <br>
 14  * &emsp;wWidth:&emsp;Word;
 15  * <br>
 16  * &emsp;wHeight&emsp;:Word;
 17  * <br>
 18  * &emsp;sTitle&emsp;:String[16];
 19  * <br>
 20  * &emsp;UpdateDate&emsp;:TDateTime;
 21  * <br>
 22  * &emsp;Reserved&emsp;: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 }
MapHeader

      (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  * &nbsp;TMapInfo = packed record
 20  * <br>
 21  * &emsp;wBkImg&emsp;:Word;
 22  * <br>
 23  * &emsp;wMidImg&emsp;:Word;
 24  * <br>
 25  * &emsp;wFrImg&emsp;:Word;
 26  * <br>
 27  * &emsp;btDoorIndex&emsp;:Byte;
 28  * <br>
 29  * &emsp;btDoorOffset&emsp;:Byte;
 30  * <br>
 31  * &emsp;btAniFrame&emsp;:Byte;
 32  * <br>
 33  * &emsp;btAniTick&emsp;:Byte;
 34  * <br>
 35  * &emsp;btArea&emsp;:Byte;
 36  * <br>
 37  * &emsp;btLight&emsp;:Byte;
 38  * </p>
 39  * <p>
 40  * type
 41  * <br>
 42  * &nbsp;TMapInfo = packed record
 43  * <br>
 44  * &emsp;wBigTileImg&emsp;:Word;
 45  * <br>
 46  * &emsp;wSmTileImg&emsp;:Word;
 47  * <br>
 48  * &emsp;wObjImg&emsp;:Word;
 49  * <br>
 50  * &emsp;btDoorIndex&emsp;:Byte;
 51  * <br>
 52  * &emsp;btDoorOffset&emsp;:Byte;
 53  * <br>
 54  * &emsp;btAniFrame&emsp;:Byte;
 55  * <br>
 56  * &emsp;btAniTick&emsp;:Byte;
 57  * <br>
 58  * &emsp;btObjFile&emsp;:Byte;
 59  * <br>
 60  * &emsp;btLight&emsp;: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  * &emsp;其中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 }
MapTile
  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 }
MapTileInfo

      對於讀取物理文件到產生對象,我使用一些工具方法,這里主要是高低位的問題,還有就是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  * &nbsp;TIndexHeader&nbsp;=&nbsp;record
17  * <br>
18  * &emsp;sTitle&emsp;:String[40];
19  * <br>
20  * &emsp;iIndexCount&emsp;: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 }
Wix

      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  * &nbsp;TLibHeader&nbsp;=&nbsp;record
20  * <br>
21  * &emsp;sTitle&emsp;:String[40];
22  * <br>
23  * &emsp;iImageCount&emsp;:Integer;
24  * <br>
25  * &emsp;iColorCount&emsp;:Integer;
26  * <br>
27  * &emsp;iPaletteSize&emsp;: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  * &nbsp;TImageInfo&nbsp;=&nbsp;record
36  * <br>
37  * &emsp;siWidth&emsp;:SmallInt;
38  * <br>
39  * &emsp;siHeight&emsp;:SmallInt;
40  * <br>
41  * &emsp;siPx&emsp;:SmallInt;
42  * <br>
43  * &emsp;siPy&emsp;:SmallInt;
44  * <br>
45  * &emsp;Bits&emsp;: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 }
Wil
  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      * &emsp;設備相關位圖 Device Depend Bitmap DDB
 29      * <br>
 30      * &emsp;設備無關位圖 Device Independ Bitmap DIB
 31      * <br>
 32      * 熱血傳奇使用DIB對圖片進行存儲
 33      * <br>
 34      * 色彩深度有時少於24位,有時是因為精度要求並不會那么高,例如使用5或6個字節存儲的R/G/B值就已經夠用,此時可使用16位顏色值存儲一個像素點的顏色
 35      * <br>
 36      * &emsp;有時甚至一幅圖的色彩不超過256種,此時就可以將這256中顏色提取出來作為一個調色板,然后像素點色彩值則存儲色彩值在這個調色板中的索引,這就是8位顏色值
 37      * <br>
 38      * &emsp;對於單色或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 }
LibHeader
  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&emsp;8&emsp;4&emsp;0&emsp;
 23      * <br>
 24      * 13&emsp;9&emsp;5&emsp;1&emsp;
 25      * <br>
 26      * 14&emsp;10&emsp;6&emsp;2&emsp;
 27      * <br>
 28      * 15&emsp;11&emsp;7&emsp;3&emsp;
 29      * <br>
 30      * 16位
 31      * <br>
 32      * 24&25&emsp;16&17&emsp;8&9&emsp;0&1&emsp;
 33      * <br>
 34      * 26&27&emsp;18&19&emsp;10&11&emsp;2&3&emsp;
 35      * <br>
 36      * 28&29&emsp;20&21&emsp;12&13&emsp;4&5&emsp;
 37      * <br>
 38      * 30&31&emsp;22&23&emsp;14&15&emsp;6&7&emsp;
 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 }
ImageInfo

    第三節——讀取:

      我們的目的在於使用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     }
readWil

    第四節——圖片處理:

      圖片數據需要經過轉換才能在界面上展示(針對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     }
readLibHeader

      在顯示時要記住圖片顏色數據的存儲是從右向左,從上往下的:

 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     }
readImage

 

  說了這么久,我自己都糊塗了。大家如果不清楚請下載源碼或基於Eclipse和JDK的項目進行查看。

 

  編輯於2015-01-25 21:06:33

    項目沒有繼續下去,不過我用lwjgl重寫了部分,實現了地圖加載和紋理讀取。大家可以去新的項目地址checkout代碼。

 

  編輯於2017-08-16 13:53:40

    github上我上傳了一個輕便的庫,可以一看https://github.com/jootnet/mir2.core

 

 歡迎您移步我們的交流群,無聊的時候大家一起打發時間:Programmer Union

 或者通過QQ與我聯系:點擊這里給我發消息

 (最后編輯時間2014-03-16 15:43:23)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM