1. 程式人生 > >[轉載]熱血傳奇之資源文件與地圖的讀取分析

[轉載]熱血傳奇之資源文件與地圖的讀取分析

thead open pda exc height 保留字 img 單位 累加

Mr.Johness
阿何的程序人生
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;


復制代碼


      每個.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類型,[email protected]

/* */ 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 * &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 }


復制代碼
復制代碼


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個地圖塊,具體到繪制地圖時則表現在只有橫縱坐標都為雙數時才繪制),[email protected]
/* */ 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 * &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 }


復制代碼


      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字節,[email protected]
/* */ WIX}。其中sTitle為標題,一般為“ILIB v1.0-WEMADE Entertainment inc.”;iImageCount為圖片數量;iColorCount表示色彩位深度,如256表示8bit位圖,65536表示16bit位圖(2的冪數);iPaletteSize表示調色板字節數</b>
30 * <br>
31 * 頭部後則是調色板([email protected] 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 }


復制代碼
復制代碼


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 }


復制代碼
復制代碼


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的倍數會有填充字節數,如果是讀取真正的圖片數據可以不用考慮,但如果需要一次性讀取多張圖片則需要跳過填充的字節,[email protected] 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 }


復制代碼
    第三節——讀取:


      我們的目的在於使用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代碼。





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


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


(最後編輯時間2014-03-16 15:43:23)
認真你就輸了,一直認真你就贏了!










傳奇2的Map文件,前面52個字節是文件頭,前4個字節是兩個Integer型的值,表示地圖的高度和寬度。其余48個字節是文件標識之類,不用管。
之後,每12個字節表示地圖上的一個坐標,在分析那12個字節之前,先說說Wil文件,
Objects? 地圖數據(地面物件)
SmTiles 構造地面的基本物件(小)
Tiles 構造地面的基本物件(大)
Map文件裏面其實就是對上面的文件的索引,從裏面得到地圖的圖像數據。
好了,現在說那12個字節,
首先一個Integer型的,表示這一塊是Tiles.Wil文件裏的第幾張圖片。不過這個值的最高的一個二進制位是表示這個地方是不是允許玩家在上面走動的。
然後又一個Integer型的,索引的SmTiles.Wil
再一個Integer型的,是對Objects的索引,首位也是可移動標記。不過那種Objects文件不只一個,第11個字節的值就是表示是第幾個Objects文件了。
第7和第8個字節是表示地圖上有動畫的,比如我們進商店裏有些有開門的動畫。
第9和10個字節我還沒弄明白,不知道具體的作用。
第12個字節也不太清楚。
知道了上面那些,寫個傳奇2的地圖查看程序,足夠了。

[轉載]熱血傳奇之資源文件與地圖的讀取分析

相關推薦

[轉載]熱血傳奇資源地圖讀取分析

thead open pda exc height 保留字 img 單位 累加 Mr.Johness阿何的程序人生JMir——Java版熱血傳奇2之資源文件與地圖  我雖然是90後,但是也很喜歡熱血傳奇2(以下簡稱“傳奇”)這款遊戲。  進入程序員行業後自己也對傳奇客戶端實

Centos常用命令目錄管理

一個 每一個 -- 目錄 clas rect 發現 linux中 使用 在centos中常用的文件與目錄操作命令有: ◇chmod:修改文件或目錄的權限 ◇mkdir:新建目錄◇rmdir:刪除目錄◇rm:刪除目錄或文件◇cp:復制目錄或文件◇mv:移動目錄或文件 下面

Pythonxlsxcsv相互轉換

shee 單元格 pre tab 轉換 寫入 strong main work 1 xlsx文件轉csv文件 import xlrd import csv def xlsx_to_csv(): workbook = xlrd.open_workbo

C#資源資源名稱字符串之間的互相轉化

NPU 資源文件 val ret gets gen ati bject log 原文:C#資源文件與與資源名稱字符串之間的互相轉化1.使用ResourceManager string st = Properties.Resources.ResourceManager.Ge

鏈接器Map符號表

must 不可 group 程序 separate 可見 多個 -m ada 一、map、全局符號及靜態符號一般的大型工程都會在生成可執行文件的同時讓鏈接器生成一個map文件,從而大致查看一下可執行文件中符號的內存布局以及從哪裏引入可執行文件。這個通常對於小型工程是作用不大

Windows App開發數據

了吧 hide asm ber 下拉列表 cached 默認 dev manager 讀取文件和目錄名 這一節開始我們將陸續看到Windows App是如何操作文件的。 在Windows上讀取文件名稱、目錄名 首先我們在XAML中定義一個But

【Unity遊戲開發】toluawrap的原理使用

nop 微信 attr hiera n) 接下來 system 作者 prim   本文內容轉載自:https://www.cnblogs.com/blueberryzzz/p/9672342.html 。非常感謝原作者慷慨地授權轉載,比心!@blueberryzzz

Linux 使用者、許可權 Linux目錄結構

  目錄結構 Linux和Windows目錄結構的組織形式有很大不同,   Windows     劃分出了“盤”的概念(C盤、D盤、E盤),已經建立檔案系統的硬碟分割槽被掛載到某一個目錄下,使用者通過操作目錄來實現磁碟讀寫。     以反斜槓(\)分割目錄   Linu

Linux初識 VMwareCentos系統安裝 Linux目錄結構

關於虛擬機器的安裝 參考:VMware與Centos系統安裝 Linux和其他語言有些不同   如果輸入命令後沒有返回(這證明輸入的命令沒錯)   如果輸入的命令有返回(命令不一定是錯的,檢視命令除外) 第一次使用Linux建議將防火牆和selinux永久關閉(個人測試) 命

Linux目錄管理 Linux目錄結構

Linux之文件與目錄結構   Linux檔案系統結構 Linux目錄結構的組織形式和Windows有很大的不同。首先Linux沒有“盤(C盤、D盤、E盤)”的概念。已經建立檔案系統的硬碟分割槽被掛載到某一個目錄下,使用者通過操作目錄來實現磁碟讀寫。 Linux不

asp.net core合並壓縮資源轉載

eve 名稱 鏈接 tails 合並 net div spn docs 在asp.net core中使用BuildBundlerMinifier合並壓縮資源文件 在asp.net mvc中可以使用Bundle來壓縮合並css,js 不知道的見:http://www.c

iOS 開發 pdf 的載入瀏覽的 4 種方式

前言 在我們的開發中,有些像電子書型別的app的開發會涉及到pdf文件的載入與展示。由於筆者專案中正好涉及到這塊,於是將pdf常用的幾種載入方式做個總結。以供後面可能用到的同學做個參考。 正文 通常我們用到的pdf文件的載入方式有4種: UIWebView載入本地或者

Amazon SimpleDB開發人員資源_教程

Amazon Web Services 誠聘精英。 Amazon Web Services (AWS) 是 Amazon.com 的一個充滿活力、不斷壯大的業務部門。我們現誠聘軟體開發工程師、產品經理、客戶經理、解決方案架構師、支援工程師、系統工程師以及設計師等人才。請訪問我

linux基礎btrfs系統管理應用

meta 文件 fault 數據校驗 rac sys 大小 pre 目標 btrfs文件系統管理與應用 1、btrfs文件系統 基本介紹   btrfs文件系統在CentOS7.x上屬於技術預覽版   btrfs文件系統英文名:B-tree FileSystem或

python死磕四IO

文件內容 getmtime recent 獲取 tempfile trac join 重定向 names   利用python進行文件操作在我們平常項目中也經常用到,下面是我日常工作中忽略或者沒有遇到過的集中情況。      一、你想將 print() 函數的輸出重定向

opensslBIO系列12---描寫敘述符(fd)類型BIO

scrip 所在 pri 返回 div static 實現 論壇 res 文件描寫敘述符(fd)類型BIO ---依據openssl doc\crypto\bio_s_fd.pod翻譯和自己的理解寫成 (作者:DragonKing [email 

將IDEA maven項目中src源代碼下的xml等資源編譯進classes

默認 文件的 ips src directory htm 文件夾 ref 編譯 如題,IDEA的maven項目中,默認源代碼目錄下的xml等資源文件並不會在編譯的時候一塊打包進classes文件夾,而是直接舍棄掉。 如果使用的是Eclipse,Eclipse的src目錄下

Python路-操作(py)

句柄 接口 編碼 操作 strong span 操作系統 使用 color 文件操作的基本步驟:   1.打開文件:f=open(‘filename‘),with open(‘filename‘) as f   2.操作文件:增,刪,改,查   3.關閉文件:f.close

ASP.NET全局防盜鏈

system 處理程序 處理 tle 盜鏈 title fly bject script 添加Web→全局應用程序類,註 文件名不要改 Global.asax全局文件是對Web應用聲明周期的一個事件響應的地方,將Web應用啟動時初始化的一些代碼寫到Application_S

小談——讀取web資源的方式和路徑問題

put mage 文件 ons 控制臺 文件的 web doget web-inf 讀取web資源文件的方式 a): 采用servletContext對象獲得.   優點: 任意文件,任意路徑都可獲得   缺點: 必須在web環境下 // 拿到全局對象