传奇地图的map文件如何转换成1:1的图片格式?

您的位置 :
ip转换器怎么用
所属专题:&
ip转换器怎么用 传奇地图素材下载 1.90皓月合击 传奇世界私服进去后黑屏 怎么快速升级热血传奇 新开1.80传奇私服 卓越玉兔 超变天龙 1.85变态英雄合击 传奇私服95神蛇 传奇沉默(2002)版本
  四个小时过后,叶城有惊无险的来到了白驼山庄。
  由于猛虎寨副本刷者甚少,一笑清风醉这篇贴子的回复率和点击率也不怎么高,六千多个点击,二十几个回复,其中有一大半还都是什么‘顶’‘我就看看’‘路过打酱油’等等的无意义刷分式回复。
  心魔追击附体的速度极快,而要想躲开,只有趁着他快到钻入体内的瞬间,不到1秒的时间进行躲避,才能逃脱他的追击。
  “马夫人,你在做什么?”乔峰又惊又怒。
  阿紫微伸雪臂,冲着叶城招了招手。
  在别的门派,玩家想要一步登天千难万难,但在星宿派这个行事非常理衡之的邪教,却是一切皆有可能。
  康敏尖叫了一声,抱住了叶城一条大腿:“主人救我,这是花蝎毒障,碰到必死。”
  乖乖神仙倒(特殊物品,阿紫独门制作):使用之后喷出一股射程为五米的毒烟,使对手在5分钟之内动弹不得,注:对内功较高对手使用时,此物威力会大打折扣,如若碰到绝顶高手,则完全无用。
  正是因为这个原因,才让叶城内心挣扎,不看吧,还想看,想看吧,又觉得不好。
  一笑清风醉脸色骤变,寨门高达五米,就算他使用轻功,也无法飞跃出去。
  司徒雅忍不住斥道:“你二啊?”
  这是李木风给叶城的留言。
  能够获得更多的经验,三个小朋友自然欢喜,不过队长我想还俗却不乐意了,对他来说,时间就是金钱,越快通关越好,他才不管坐车的人经验是多是少呢。
  “我的!”
  云中鹤来此,显然是还不知道司空倾月已经脱困,他不是玩家,只是一个剧情人物,他依照线索一路找来,和能够相隔各地瞬间交流的玩家相比,知道消息的速度迟缓太多了。
  邪傲书生的脸色更难看了。
  噗――
  将武器精炼,得到更强大的属性,这就是精炼系统存在的意义。
  “苏娜小姐,像这种地摊货,我不用工作也能买到很多条。”
  白驼山庄主管卜雪阳虽然不像欧阳锋和欧阳克那么出名,但如果没有点实力,又怎能当上山庄总管?
  叶城一边扶她,一边向苏娜皱眉:“怎么叫她喝的这么多?”
  噗!噗!噗!
  看着欧阳克搂着‘小咪’和‘小灵’将要进房,叶城做下了一个决定。
  安颜也不知道该怎么办才好了,瞪着天真无邪黑白分明的大眼睛,看着花小花,让她拿主意。
  “老衲来也,看头!”
  原来在副本模式里产生的破坏,会直接影响考验的评价……
  安颜除了呆一点,要论长相,比方柔至少强上十几倍,这样一位大美女跑到叶城面前,规规距距的喊师父,要说傲剑哥不嫉妒,那绝对是假的。
  “来城哥,咱们不理他,亲个……”
ip转换器怎么用
【】【】【】
【】【】【】您的举报已经提交成功,我们将尽快处理,谢谢!
现实世界中军事设施地图的编辑器可是有着高度机密的,知道也不能告诉你。在爱问知识人首页的左上角有游戏--网络游戏栏,那里会有人告诉你虚拟世界的答案。
打开工程后,选择菜单Project-&Settings,在对话框里选Link选项卡,Category选Debug,你就会看到有个选项是Generate m...
是不是观看录相的时候右上角的地图为黑?
你双击一下那个录相文件,上面会提示你地图要放置的位置,手动建立文件夹,再把地图考进去就可以了。
如果是建立游戏没有地...
大家还关注BSP转MAP地图文件转换器(WinBSPC)下载 v1.2绿色中文版_ - pc6下载站JMir——Java版热血传奇2之资源文件与地图 - Mr.Johness - 博客园
阿何的程序人生
  我虽然是90后,但是也很喜欢热血传奇2(以下简称&传奇&)这款游戏。
  进入程序员行业后自己也对传奇客户端实现有所研究,现在将我的一些研究结果展示出来,如果大家有兴趣的话不妨与我交流。
  项目我托管到上了,使用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:&W
3 &wHeight&:W
4 &sTitle&:String[16];
5 &UpdateDate&:TDateT
6 &Reserved&:array[0..22] of C
      (Tile,两种都可以):
TMapInfo = packed record
3 &wBkImg&:W
4 &wMidImg&:W
5 &wFrImg&:W
6 &btDoorIndex&:B
7 &btDoorOffset&:B
8 &btAniFrame&:B
9 &btAniTick&:B
10 &btArea&:B
11 &btLight&:B
TMapInfo = packed record
15 &wBigTileImg&:W
16 &wSmTileImg&:W
17 &wObjImg&:W
18 &btDoorIndex&:B
19 &btDoorOffset&:B
20 &btAniFrame&:B
21 &btAniTick&:B
22 &btObjFile&:B
23 &btLight&:B
      每个.map文件如果在Delphi中就成了一个TMapHeader加wWidth*wHeight个MapTile。
      (对于每个字段占用的字节数请查看下面Java代码中注释)
      由于我们是使用Java语言描述热血传奇地图,所以我针对上述两个数据结构使用Java语言进行了描述:
1 package org.coderecord.jmir.entt.
3 import java.util.D
* 热血传奇2地图文件头
* 针对*.map文件的数据结构使用Java语言描述
* 地图文件头为52字节,在Pascal中定义为
* &TMapHeader = packed record
* &wWidth:&W
* &wHeight&:W
* &sTitle&:String[16];
* &UpdateDate&:TDateT
* &Reserved&:array[0..22] of C
* &b&wWidth&/b& 表示地图宽度(占用两个字节,相当于Java语言short;一般不超过1000)
* &b&wHeight&/b& 表示地图高度(占用两个字节,相当于Java于洋short;一般不超过1000)
* &b&sTitle&/b& 标题,静态单字符串(占用17个字节,首字节为字符串已使用的长度即已存放的字符数,一般为&Legend of mir&)
* &b&UpdateDate&/b& 地图最后更新时间(占用8个字节,为TDateTime类型,可使用{@link org.coderecord.jmir.kits.Pascal#readDate(byte[], int, boolean) readDate} 转换为java.util.Date)
* &b&Reserved&/b& 保留字符,固定为23字节
* @author ShawRyan
39 public class MapHeader {
/** 地图宽度(横向长度) */
private short
/** 地图高度(纵向长度) */
private short
/** 标题 */
/** 更新日期 */
private Date updateD
/** 保留字符 */
private char[]
/** 默认构造函数 */
public MapHeader() {}
/** 带全部参数的构造函数 */
public MapHeader(short width, short height, String title, Date updateDate, char[] reserved) {
this.width =
this.height =
this.title =
this.updateDate = updateD
this.reserved =
/** 使用已有对象构造实例 */
public MapHeader(MapHeader mapHeader) {
this.width = mapHeader.getWidth();
this.height = mapHeader.getHeight();
this.title = mapHeader.getTitle();
this.updateDate = mapHeader.getUpdateDate();
this.reserved = mapHeader.getReserved();
/** 获取地图宽度(横向长度) */
public short getWidth() {
/** 设置地图宽度(横向长度) */
public void setWidth(short width) {
this.width =
/** 获取地图高度(纵向长度) */
public short getHeight() {
/** 设置地图高度(纵向长度) */
public void setHeight(short height) {
this.height =
/** 获取标题 */
public String getTitle() {
/** 设置标题 */
public void setTitle(String title) {
this.title =
/** 获取更新时间 */
public Date getUpdateDate() {
return updateD
/** 设置更新时间 */
public void setUpdateDate(Date updateDate) {
this.updateDate = updateD
/** 获取保留字符 */
public char[] getReserved() {
/** 设置保留字符 */
public void setReserved(char[] reserved) {
this.reserved =
      (Tile我使用了两种描述方式,后一种用于生产环境更加优秀):
1 package org.coderecord.jmir.entt.
* 热血传奇2地图&块&
* 即 &&b&逻辑坐标&/b&&点(人物/NPC等放置需要占用一个逻辑坐标点)
* 需要注意的是逻辑坐标和屏幕坐标是不一样的,屏幕坐标一般为像素值,根据显示器分辨率设置而有所不同
* 热血传奇2中一个逻辑坐标点(地图块)需要占用 48 * 32 屏幕坐标大小
* 每个地图块为2层结构,包括&地&和&空&
* 例如树叶投影下的地图块就是2层,包括地表及物体(如有突起石头的地面或有水流的地面)和树叶
* 在Pascal语言中使用以下数据结构对地图块进行描述和存储(两种)
* &TMapInfo = packed record
* &wBkImg&:W
* &wMidImg&:W
* &wFrImg&:W
* &btDoorIndex&:B
* &btDoorOffset&:B
* &btAniFrame&:B
* &btAniTick&:B
* &btArea&:B
* &btLight&:B
* &TMapInfo = packed record
* &wBigTileImg&:W
* &wSmTileImg&:W
* &wObjImg&:W
* &btDoorIndex&:B
* &btDoorOffset&:B
* &btAniFrame&:B
* &btAniTick&:B
* &btObjFile&:B
* &btLight&:B
* &b&wBkImg&/b&或&b&wBigTileImg&/b& 表示地图地表图片,如果最高位为1则表示不能通过(或站立),如河水型地表等。在判断是否可以飞过(从空中通过)时则不需要考虑
* &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)后就不需要绘制此层图片了
* &b&wFrImg&/b&或&b&wObjImg&/b& 表示表层图片(对象),即空中遮挡物,如植物或建筑物,如果最高位为1则表示不能通过(或站立)。在判断是否可飞过(从空中通过)时需要作为唯一条件判断,在判断是否可以徒步通过或站立时需要联合wBkImg进行判断
* &b&总的来说,地图一般为两层(只是针对上面的三个属性,下方的也属于地图部分,不过先不纳入考虑),包括背景层与对象层,背景层为wBkImg(或wBigTileImg)和wMidImg(或wSmTileImg)的集合,一般来说wBkImg就能搞定,也有时候需要两者都有;Spirit(人物/怪物/NPC/掉落物品等)在两层中间;索引从1开始,所以在从资源中真正取图片时应该减1(适用于所有资源索引);索引一般最高位为0,为1一般表示特殊情况(在Java语言中可以理解为大于0,因为首位为1表示负数)&/b&
* &b&btDoorIndex&/b& 门索引,最高位为1表示有门,为0表示没有门。
* &b&btDoorOffset&/b& 门偏移,最高位为1表示门打开了,为0表示门为关闭状态
* &b&btAniFrame&/b& 帧数,指示当前地图块动态内容由多少张静态图片轮询播放,需要和btAniTick一起起作用;如果最高位为1(即值大于0x80,或者在Java中为小于0的数值)则表示有动态内容
* &b&btAniTick&/b& 跳帧数,指示当前地图块动态内容应该每隔多少帧变换当前显示的静态图片,需要和btAniFrame一起作用
* &b&btAniFrame和btAniTick作用时表达式如下index = (gAniCount % (btAniFrame * (1 + btAniTick))) / (1 + btAniTick)
* &其中gAniCount是当前画面帧是第几帧,它会在每次绘制游戏界面时累加,它可以有最大值,超过可以置0;index是相对当前objImgIdx的偏移量,比如当前对象层图片索引为1,而AniFrame为10,则表示从1到11这10副图片应该作为一动态内容播放(有待考证)
* &b&btArea&/b&或&b&btObjFile&/b& 表示当前wFrImg(或wObjImg)和动态内容构成图片来自哪个Object资源文件,具体为Object{btArea}.wil中,如果btArea为0则是Objects.wil
* &b&btLight&/b& 亮度,一般为0/1/4
* @author ShawRyan
91 public class MapTile {
/** 背景图索引 */
private short bngImgI
/** 补充背景图索引 */
private short midImgI
/** 对象图索引 */
private short objImgI
/** 门索引 */
private byte doorI
/** 门偏移 */
private byte doorO
/** 动画帧数 */
private byte aniF
/** 动画跳帧数 */
private byte aniT
/** 资源文件索引 */
private byte objFileI
/** 亮度 */
private byte
/** 默认构造函数 */
public MapTile() { }
/** 使用已有对象构造实例 */
public MapTile(MapTile mapTile) {
this.bngImgIdx = mapTile.bngImgI
this.midImgIdx = mapTile.midImgI
this.objImgIdx = mapTile.objImgI
this.doorIdx = mapTile.doorI
this.doorOffset = mapTile.doorO
this.aniFrame = mapTile.aniF
this.aniTick = mapTile.aniT
this.objFileIdx = mapTile.objFileI
this.light = mapTile.
/** 带全部参数的构造函数 */
public MapTile(short bngImgIdx, short midImgIdx, short objImgIdx, byte doorIdx, byte doorOffset, byte aniFrame, byte aniTick, byte objFileIdx, byte light) {
this.bngImgIdx = bngImgI
this.midImgIdx = midImgI
this.objImgIdx = objImgI
this.doorIdx = doorI
this.doorOffset = doorO
this.aniFrame = aniF
this.aniTick = aniT
this.objFileIdx = objFileI
this.light =
/** 获取背景图索引 */
public short getBngImgIdx() {
return bngImgI
/** 设置背景图索引 */
public void setBngImgIdx(short bngImgIdx) {
this.bngImgIdx = bngImgI
/** 获取补充图索引 */
public short getMidImgIdx() {
return midImgI
/** 设置补充图索引 */
public void setMidImgIdx(short midImgIdx) {
this.midImgIdx = midImgI
/** 获取对象图索引 */
public short getObjImgIdx() {
return objImgI
/** 设置对象图索引 */
public void setObjImgIdx(short objImgIdx) {
this.objImgIdx = objImgI
/** 获取门索引 */
public byte getDoorIdx() {
return doorI
/** 设置门索引 */
public void setDoorIdx(byte doorIdx) {
this.doorIdx = doorI
/** 获取门偏移 */
public byte getDoorOffset() {
return doorO
/** 设置门偏移 */
public void setDoorOffset(byte doorOffset) {
this.doorOffset = doorO
/** 获取动画帧数 */
public byte getAniFrame() {
return aniF
/** 设置动画帧数 */
public void setAniFrame(byte aniFrame) {
this.aniFrame = aniF
/** 获取动画跳帧数 */
public byte getAniTick() {
return aniT
/** 设置动画跳帧数 */
public void setAniTick(byte aniTick) {
this.aniTick = aniT
/** 获取资源文件索引 */
public byte getObjFileIdx() {
return objFileI
/** 设置资源文件索引 */
public void setObjFileIdx(byte objFileIdx) {
this.objFileIdx = objFileI
/** 获取亮度 */
public byte getLight() {
/** 设置亮度 */
public void setLight(byte light) {
this.light =
1 package org.coderecord.jmir.entt.
3 import org.coderecord.jmir.scn.DrawS
* MapTile方便程序逻辑的另类解读方式
* @author ShawRyan
11 public class MapTileInfo {
/** 背景图索引 */
private short bngImgI
/** 是否有背景图(在热血传奇2地图中,背景图大小为4个地图块,具体到绘制地图时则表现在只有横纵坐标都为双数时才绘制),见{@link DrawSupport#drawMap(int, int, org.coderecord.jmir.entt.Map, int, int) drawMap} */
private boolean hasB
/** 是否可行走(站立) */
private boolean canW
/** 补充背景图索引 */
private short midImgI
/** 是否有补充图 */
private boolean hasM
/** 对象图索引 */
private short objImgI
/** 是否有对象图 */
private boolean hasO
/** 是否可以飞越 */
private boolean canF
/** 门索引 */
private byte doorI
/** 是否有门 */
private boolean hasD
/** 门偏移 */
private byte doorO
/** 门是否开启 */
private boolean doorO
/** 动画帧数 */
private byte aniF
/** 是否有动画 */
private boolean hasA
/** 动画跳帧数 */
private byte aniT
/** 资源文件索引 */
private byte objFileI
/** 亮度 */
private byte
/** 无参构造函数 */
public MapTileInfo() { }
/** 带全部参数的构造函数 */
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) {
this.bngImgIdx = bngImgI
this.hasBng = hasB
this.canWalk = canW
this.midImgIdx = midImgI
this.hasMid = hasM
this.objImgIdx = objImgI
this.hasObj = hasO
this.canFly = canF
this.doorIdx = doorI
this.hasDoor = hasD
this.doorOffset = doorO
this.doorOpen = doorO
this.aniFrame = aniF
this.hasAni = hasA
this.aniTick = aniT
this.objFileIdx = objFileI
this.light =
/** 基于已有实体构造对象 */
public MapTileInfo(MapTileInfo mapTileInfo) {
this.bngImgIdx = mapTileInfo.bngImgI
this.hasBng = mapTileInfo.hasB
this.canWalk = mapTileInfo.canW
this.midImgIdx = mapTileInfo.midImgI
this.hasMid = mapTileInfo.hasM
this.objImgIdx = mapTileInfo.objImgI
this.hasObj = mapTileInfo.hasO
this.canFly = mapTileInfo.canF
this.doorIdx = mapTileInfo.doorI
this.hasDoor = mapTileInfo.hasD
this.doorOffset = mapTileInfo.doorO
this.doorOpen = mapTileInfo.doorO
this.aniFrame = mapTileInfo.aniF
this.hasAni = mapTileInfo.hasA
this.aniTick = mapTileInfo.aniT
this.objFileIdx = mapTileInfo.objFileI
this.light = mapTileInfo.
/** 获取背景图索引 */
public short getBngImgIdx() {
return bngImgI
/** 设置背景图索引 */
public void setBngImgIdx(short bngImgIdx) {
this.bngImgIdx = bngImgI
/** 获取该地图块是否有背景图 */
public boolean isHasBng() {
return hasB
/** 设置该地图块是否有背景图 */
public void setHasBng(boolean hasBng) {
this.hasBng = hasB
/** 获取该地图块是否可以站立或走过 */
public boolean isCanWalk() {
return canW
/** 设置该地图块是否可以站立或走过 */
public void setCanWalk(boolean canWalk) {
this.canWalk = canW
/** 获取补充图索引 */
public short getMidImgIdx() {
return midImgI
/** 设置补充图索引 */
public void setMidImgIdx(short midImgIdx) {
this.midImgIdx = midImgI
/** 获取该地图块是否有补充图 */
public boolean isHasMid() {
return hasM
/** 设置该地图块是否有补充图 */
public void setHasMid(boolean hasMid) {
this.hasMid = hasM
/** 获取对象图索引 */
public short getObjImgIdx() {
return objImgI
/** 设置对象图索引 */
public void setObjImgIdx(short objImgIdx) {
this.objImgIdx = objImgI
/** 获取该地图块是否有对象图 */
public boolean isHasObj() {
return hasO
/** 设置该地图块是否有对象图 */
public void setHasObj(boolean hasObj) {
this.hasObj = hasO
/** 获取该地图块是否可以飞越 */
public boolean isCanFly() {
return canF
/** 设置该地图块是否可以飞越 */
public void setCanFly(boolean canFly) {
this.canFly = canF
/** 获取门索引 */
public byte getDoorIdx() {
return doorI
/** 设置门索引 */
public void setDoorIdx(byte doorIdx) {
this.doorIdx = doorI
/** 获取该地图块是否有门 */
public boolean isHasDoor() {
return hasD
/** 设置该地图块是否有门 */
public void setHasDoor(boolean hasDoor) {
this.hasDoor = hasD
/** 获取门偏移 */
public byte getDoorOffset() {
return doorO
/** 设置门偏移 */
public void setDoorOffset(byte doorOffset) {
this.doorOffset = doorO
/** 获取该地图块门是否打开 */
public boolean isDoorOpen() {
return doorO
/** 设置该地图块门是否打开 */
public void setDoorOpen(boolean doorOpen) {
this.doorOpen = doorO
/** 获取动画帧数 */
public byte getAniFrame() {
return aniF
/** 设置动画帧数 */
public void setAniFrame(byte aniFrame) {
this.aniFrame = aniF
/** 获取该地图块是否有动画 */
public boolean isHasAni() {
return hasA
/** 设置该地图块是否有动画 */
public void setHasAni(boolean hasAni) {
this.hasAni = hasA
/** 获取动画跳帧数 */
public byte getAniTick() {
return aniT
/** 设置动画跳帧数 */
public void setAniTick(byte aniTick) {
this.aniTick = aniT
/** 获取资源文件索引 */
public byte getObjFileIdx() {
return objFileI
/** 设置资源文件索引 */
public void setObjFileIdx(byte objFileIdx) {
this.objFileIdx = objFileI
/** 获取亮度 */
public byte getLight() {
/** 设置亮度 */
public void setLight(byte light) {
this.light =
MapTileInfo
      对于读取物理文件到产生对象,我使用一些工具方法,这里主要是高低位的问题,还有就是Delphi中字符串和时间日期到Java的不同处理。
    第三节&&读取:
      我对.map文件读取数据生成对象的过程如下(工具方法请查阅源码,就不在此贴出了):
* 通过字节数组反序列化地图文件头数据
* @param bytes
数据(文件中直接读取,未经过任何处理的字节数组)
地图文件头信息
public static MapHeader readMapHeader(byte[] bytes) {
MapHeader res = new MapHeader();
res.setWidth(Common.readShort(bytes, 0, true));
res.setHeight(Common.readShort(bytes, 2, true));
res.setTitle(readStaticSingleString(bytes, 4));
res.setUpdateDate(readDate(bytes, 21, true));
res.setReserved(readChars(bytes, 29, 23));
* 通过字节数组反序列化地图逻辑坐标块儿数据
* @param bytes
数据(文件中直接读取,未经过任何处理的字节数组)
地图逻辑坐标块儿信息
public static MapTile readMapTile(byte[] bytes) {
MapTile res = new MapTile();
res.setBngImgIdx(Common.readShort(bytes, 0, true));
res.setMidImgIdx(Common.readShort(bytes, 2, true));
res.setObjImgIdx(Common.readShort(bytes, 4, true));
res.setDoorIdx(bytes[6]);
res.setDoorOffset(bytes[7]);
res.setAniFrame(bytes[8]);
res.setAniTick(bytes[9]);
res.setObjFileIdx(bytes[10]);
res.setLight(bytes[11]);
* 通过字节数组反序列化地图逻辑坐标块信息
* @param bytes
数据(文件中直接读取,未经过任何处理的字节数组)
地图逻辑坐标块儿信息
public static MapTileInfo readMapTileInfo(byte[] bytes) {
MapTileInfo res = new MapTileInfo();
// 读取背景
short bng = Common.readShort(bytes, 0, true);
// 读取中间层
short mid = Common.readShort(bytes, 2, true);
// 读取对象层
short obj = Common.readShort(bytes, 4, true);
// 设置背景
if((bng & 0b11_1111) & 0) {
res.setBngImgIdx((short) (bng & 0b11_1111));
res.setHasBng(true);
// 设置中间层
if((mid & 0b11_1111) & 0) {
res.setMidImgIdx((short) (mid & 0b11_1111));
res.setHasMid(true);
// 设置对象层
if((obj & 0b11_1111) & 0) {
res.setObjImgIdx((short) (obj & 0b11_1111));
res.setHasObj(true);
// 设置是否可站立
res.setCanWalk(!Common.is1AtTopDigit(bng) && !Common.is1AtTopDigit(obj));
// 设置是否可飞行
res.setCanFly(!Common.is1AtTopDigit(obj));
res.setDoorOffset(bytes[7]);
if(Common.is1AtTopDigit(bytes[7])) res.setDoorOpen(true);
res.setAniFrame(bytes[8]);
if(Common.is1AtTopDigit(bytes[8])) {
res.setAniFrame((byte) (bytes[8] & 0x7F));
res.setHasAni(true);
res.setAniTick(bytes[9]);
res.setObjFileIdx(bytes[10]);
res.setLight(bytes[11]);
  第二部分&&资源文件:
    第一节&&描述:
      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语言描述(起始位置数组没有加上):
TIndexHeader = record
3 &sTitle&:String[40];
4 &iIndexCount&:I
        Java语言描述:
1 package org.coderecord.jmir.
* WIX即&WEMADE IMAGE INDEX&
* 意味图片索引
* 在热血传奇2中,图片资源存储方式一般为一个wix文件和一个wil文件形成一组,负责保存一个内容或功能的图片资源
* wix文件为索引文件,通过它从wil文件中解析出图片数据
* 对于wix文件(头)可使用如下数据结构进行描述
* &TIndexHeader&=&record
* &sTitle&:String[40];
* &iIndexCount&:I
* &b&sTitle一般为&INDX v1.0-WEMADE Entertainment inc.&,表示标题;iIndexCount表示对应wil(lib库文件)中存储的图片数量;其实重点是此文件中前44个字节用处不大,从45字节开始才是我们关心的。&/b&
&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字节
* 从第49字节开始则是iIndexCount个Integer类型数据,标识在对应wil文件中各个图片数据对应位置(array of Integer),这里的位置索引从0开始,指示在文件二进制数据的索引
* @author ShawRyan
31 public class WIX {
/** 标题 */
/** 资源数量 */
private int indexC
/** 资源数据起始位置 */
private int[] indexA
/** 默认构造函数 */
public WIX() {}
/** 基于已有对象构造实例 */
public WIX(WIX wix) {
this.title = wix.
this.indexCount = wix.indexC
this.indexArray = wix.indexA
/** 使用全部参数构造实例 */
public WIX(String title, int indexCount, int[] indexArray) {
this.title =
this.indexCount = indexC
this.indexArray = indexA
/** 获取标题 */
public String getTitle() {
/** 设置标题 */
public void setTitle(String title) {
this.title =
/** 获取资源数量 */
public int getIndexCount() {
return indexC
/** 设置资源数量 */
public void setIndexCount(int indexCount) {
this.indexCount = indexC
/** 获取资源索引 */
public int[] getIndexArray() {
return indexA
/** 设置资源索引 */
public void setIndexArray(int[] indexArray) {
this.indexArray = indexA
      wil文件:
        Delphi语言描述(调色板未加上,调色板说起来比较麻烦):
TLibHeader = record
3 &sTitle&:String[40];
4 &iImageCount&:I
5 &iColorCount&:I
6 &iPaletteSize&:I
TImageInfo = record
3 &siWidth&:SmallI
4 &siHeight&:SmallI
5 &siPx&:SmallI
6 &siPy&:SmallI
7 &Bits&:PB
      Java语言描述:
1 package org.coderecord.jmir.
3 import org.coderecord.jmir.entt.internal.ImageI
4 import org.coderecord.jmir.entt.internal.LibH
* WIL即&WEMADE IMAGE LIBRARY&
* 意为图片库
* 存放多个图片数据(包括像素点色彩)
* WIL文件由头部和多个图片信息组成
* 头部可以使用如下类型进行描述
* &TLibHeader&=&record
* &sTitle&:String[40];
* &iImageCount&:I
* &iColorCount&:I
* &iPaletteSize&:I
* &b&头部共占用56字节,sTitle占用44字节,原因参见{@link WIX}。其中sTitle为标题,一般为&ILIB v1.0-WEMADE Entertainment inc.&;iImageCount为图片数量;iColorCount表示色彩位深度,如256表示8bit位图,65536表示16bit位图(2的幂数);iPaletteSize表示调色板字节数&/b&
* 头部后则是调色板(调色板请参照{@link org.coderecord.jmir.entt.internal.LibHeader#pallette LibHeader})和多个图片数据,包括图片宽高/坐标和像素色彩,可以使用下面的结构进行描述(不包括调色板,调色板其实也可放入header中,类型为 array of Byte)
* &TImageInfo&=&record
* &siWidth&:SmallI
* &siHeight&:SmallI
* &siPx&:SmallI
* &siPy&:SmallI
* &Bits&:PB
* &b&siWidth&/b& 图片宽度
* &b&siHeight&/b& 图片高度
* &b&siPx&/b& 图片横向偏移量
* &b&siPy&/b& 图片纵向偏移量
* &b&Bits&/b& 图片像素色彩值数组
* @author ShawRyan
60 public class WIL {
/** 文件头 */
private LibH
/** 图片数组 */
private ImageInfo[]
/** 无参构造函数 */
public WIL() {}
/** 基于已有实例构造对象 */
public WIL(WIL wil) {
this.header = wil.
this.images = wil.
/** 全参构造函数 */
public WIL(LibHeader header, ImageInfo[] images) {
this.header =
this.images =
/** 获取头部 */
public LibHeader getHeader() {
/** 设置头部 */
public void setHeader(LibHeader header) {
this.header =
/** 获取图片 */
public ImageInfo[] getImages() {
/** 设置图片 */
public void setImages(ImageInfo[] images) {
this.images =
1 package org.coderecord.jmir.entt.
3 /** WIL文件头 */
4 public class LibHeader {
/** 长度(字节数),不包括调色板大小 */
public static final int BIT_LENGTH_WITHOUT_PATTELE = 56;
/** 标题 */
/** 图片数量 */
private int imageC
/** 色深度 */
private int colorC
/** 调色板字节数 */
private int paletteS
* 调色板使用是一组字节数组即二维字节数组,第二维总为4字节,依次存储蓝、绿、红、Alpha色彩值
* 对于任意色彩而言,都应该使用24位来存储,比如FF0000表示红色
* 24位色彩值也被称为真彩
* Windows 中位图有两种格式:
* &设备相关位图 Device Depend Bitmap DDB
* &设备无关位图 Device Independ Bitmap DIB
* 热血传奇使用DIB对图片进行存储
* 色彩深度有时少于24位,有时是因为精度要求并不会那么高,例如使用5或6个字节存储的R/G/B值就已经够用,此时可使用16位颜色值存储一个像素点的颜色
* &有时甚至一幅图的色彩不超过256种,此时就可以将这256中颜色提取出来作为一个调色板,然后像素点色彩值则存储色彩值在这个调色板中的索引,这就是8位颜色值
* &对于单色或16色(即使用1bit或4bit存储色彩值的情况不与考虑)
private int[]
/** 无参构造函数 */
public LibHeader() {}
/** 基于已有对象构造实例 */
public LibHeader(LibHeader header) {
this.title = header.
this.imageCount = header.imageC
this.colorCount = header.colorC
this.paletteSize = header.paletteS
this.palette = header.
/** 带全部参数的构造函数 */
public LibHeader(String title, int imageCount, int colorCount, int paletteSize, int[] pallette) {
this.title =
this.imageCount = imageC
this.colorCount = colorC
this.paletteSize = paletteS
this.palette =
/** 获取标题 */
public String getTitle() {
/** 设置标题 */
public void setTitle(String title) {
this.title =
/** 获取图片数量 */
public int getImageCount() {
return imageC
/** 设置图片数量 */
public void setImageCount(int imageCount) {
this.imageCount = imageC
/** 获取色深 */
public int getColorCount() {
return colorC
/** 设置色深 */
public void setColorCount(int colorCount) {
this.colorCount = colorC
/** 获取调色板大小 */
public int getPaletteSize() {
return paletteS
/** 设置调色板大小 */
public void setPaletteSize(int paletteSize) {
this.paletteSize = paletteS
/** 获取调色板 */
public int[] getPalette() {
/** 设置调色板 */
public void setPalette(int[] pallette) {
this.palette =
1 package org.coderecord.jmir.entt.
3 /** 图片信息 */
4 public class ImageInfo {
/** 图片宽度 */
private short
/** 图片高度 */
private short
/** 图片横向偏移量 */
private short offsetX;
/** 图片纵向偏移量 */
private short offsetY;
* 图片数据
* 逐列存储像素色彩值,对于4 * 4的图片而言,其色彩数据如下(以字节为单位)
* &b&注意:&/b&如果图片宽度字节数不是4的倍数会有填充字节数,如果是读取真正的图片数据可以不用考虑,但如果需要一次性读取多张图片则需要跳过填充的字节,参见{@link org.coderecord.jmir.kits.Pascal#fillPByteLineWidth(int, int) fillPByteLineWidth}
* 12&8&4&0&
* 13&9&5&1&
* 14&10&6&2&
* 15&11&7&3&
* 24&25&16&17&8&9&0&1&
* 26&27&18&19&10&11&2&3&
* 28&29&20&21&12&13&4&5&
* 30&31&22&23&14&15&6&7&
private byte[]
/** 无参构造函数 */
public ImageInfo() {}
/** 基于已有对象构造实例 */
public ImageInfo(ImageInfo imageInfo) {
this.height = imageInfo.
this.offsetX = imageInfo.offsetX;
this.offsetY = imageInfo.offsetY;
this.pixels = imageInfo.
this.width = imageInfo.
/** 带全部参数的构造函数 */
public ImageInfo(short width, short height, short offsetX, short offsetY, byte[] pixels) {
this.width =
this.height =
this.offsetX = offsetX;
this.offsetY = offsetY;
this.pixels =
/** 获取图片宽度 */
public short getWidth() {
/** 设置图片高度 */
public void setWidth(short width) {
this.width =
/** 获取图片高度 */
public short getHeight() {
/** 设置图片高度 */
public void setHeight(short height) {
this.height =
/** 获取图片横线偏移量 */
public short getOffsetX() {
return offsetX;
/** 设置图片横向偏移量 */
public void setOffsetX(short offsetX) {
this.offsetX = offsetX;
/** 获取图片纵向偏移量 */
public short getOffsetY() {
return offsetY;
/** 设置图片纵向偏移量 */
public void setOffsetY(short offsetY) {
this.offsetY = offsetY;
/** 获取图片二进制数据 */
public byte[] getPixels() {
/** 设置图片二进制数据 */
public void setPixels(byte[] pixels) {
this.pixels =
    第三节&&读取:
      我们的目的在于使用wil中的图片,在上图其实可以看到我们只差一个每个图片色彩数据大小(??处),这个大小可以自己计算得到(涉及到Delphi位图处理,信息量较大,不在此赘述),但我们也可以从wix中拿到,这样比较方便,而且不会出错。
* 根据库文件路径和索引文件路径读取图片库
* @param wilPath
图片库文件路径
* @param wixPath
图片索引文件路径
得到的图片库文件路径
public static WIL readWILFromFile(String wilPath, String wixPath) {
WIX wix = new WIX();
try(FileInputStream fis = new FileInputStream(wixPath)) {
wix.setTitle(Pascal.readStaticSingleString(fis, 0, 44));
wix.setIndexCount(Common.readInt(fis, 0, true));
int[] imageIndexs = new int[wix.getIndexCount()];
for(int i = 0; i & imageIndexs. ++i)
imageIndexs[i] = Common.readInt(fis, 0, true);
wix.setIndexArray(imageIndexs);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
WIL res = new WIL();
try(FileInputStream fis = new FileInputStream(wilPath)) {
res.setHeader(Pascal.readLibHeader(fis));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
try(RandomAccessFile raf = new RandomAccessFile(wilPath, "r")){
ImageInfo[] images = new ImageInfo[res.getHeader().getImageCount()];
for(int i = 0; i & images. ++i)
images[i] = Pascal.readImageInfo(raf, wix.getIndexArray()[i], Pascal.colorCountToBitCount(res.getHeader().getColorCount()));
res.setImages(images);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
    第四节&&图片处理:
      图片数据需要经过转换才能在界面上展示(针对8位的图片):
      首先在读取LibHeader时就需要做透明色处理:
* 从流中读取图片库文件头
* @param is
* @throws IOException
可能的流异常
public static LibHeader readLibHeader(InputStream is) throws IOException {
LibHeader res = new LibHeader();
res.setTitle(Pascal.readStaticSingleString(is, 0, 44));
res.setImageCount(Common.readInt(is, 0, true));
res.setColorCount(Common.readInt(is, 0, true));
res.setPaletteSize(Common.readInt(is, 0, true));
int[] palette = new int[res.getPaletteSize() / 4];
for(int i = 0; i & palette. ++i) {
byte[] byteArgb = new byte[4];
is.read(byteArgb);
// 最重要的一步,重设Alpha值
// 调色板的Alpha值都为0,而实际上只有0x(一般在调色板第一个色彩)才是透明色,即热血传奇2图片库中8位色彩没有不透明的纯黑色
if(byteArgb[2] == 0 && byteArgb[1] == 0 && byteArgb[0] == 0)
byteArgb[3] = 0;
byteArgb[3] = (byte) 255;
palette[i] = Common.readInt(byteArgb, 0, true);
res.setPalette(palette);
readLibHeader
      在显示时要记住图片颜色数据的存储是从右向左,从上往下的:
* 从ImageInfo对象及调色板和色彩深度读取图片数据
* @param imageInfo
图片原数据信息
* @param palette
* @param bitCount
public static BufferedImage readImage(ImageInfo imageInfo, int[] palette, int bitCount) {
BufferedImage res = null;
if(bitCount == 8) {
res = new BufferedImage(imageInfo.getWidth(), imageInfo.getHeight(), BufferedImage.TYPE_INT_ARGB);
int index = 0;
for(int h = 0; h & imageInfo.getHeight(); ++h)
for(int w = 0; w & imageInfo.getWidth(); ++w)
res.setRGB(w, res.getHeight() - 1 - h, palette[imageInfo.getPixels()[index++] & 0xff]);
} else if(bitCount == 16) {
res = new BufferedImage(imageInfo.getWidth(), imageInfo.getHeight(), BufferedImage.TYPE_USHORT_565_RGB);
int index = 0;
for(int h = 0; h & imageInfo.getHeight(); ++h)
for(int w = 0; w & imageInfo.getWidth(); ++w, index += 2)
res.setRGB(w, res.getHeight() - 1 - h, Common.readShort(imageInfo.getPixels(), index, true));
} else if(bitCount == 24) {
//res = new BufferedImage(imageInfo.getWidth(), imageInfo.getHeight(), BufferedImage.TYPE_INT_RGB);
//int index = 0;
//for(int h = 0; h & imageInfo.getHeight(); ++h)
for(int w = 0; w & imageInfo.getWidth(); ++w)
res.setRGB(w, h, Common.readInt(imageInfo.getPixels(), index++, true));
//res = new BufferedImage(imageInfo.getWidth(), imageInfo.getHeight(), BufferedImage.TYPE_INT_RGB);
//int index = 0;
//for(int h = 0; h & imageInfo.getHeight(); ++h)
for(int w = 0; w & imageInfo.getWidth(); ++w)
res.setRGB(w, h, Common.readInt(imageInfo.getPixels(), index++, true));
  说了这么久,我自己都糊涂了。大家如果不清楚请下载或基于Eclipse和JDK的进行查看。
  编辑于&21:06:33
    项目没有继续下去,不过我用lwjgl重写了部分,实现了地图加载和纹理读取。大家可以去checkout代码。
&欢迎您移步我们的交流群,无聊的时候大家一起打发时间:
&或者通过QQ与我联系:
&(最后编辑时间&15:43:23)
评论 - 112

我要回帖

更多关于 传世挂机 的文章

 

随机推荐