TiledMap斜45度角地图优化——数据文件

TiledMap数据文件分析

最近手上有个新项目,地图比较大,1201x1021块的地图,TiledMap默认的存储采用的是xml格式,Layer上的Tiled数据可以使用base64+gzip压缩的方式存储,因为都是字符串,使用压缩方式存储后地图文件本身文件大小不是什么问题,但是在运行时解析地图数据时,需要解压原始的压缩数据,base64解码,耗费大量的内存,内存峰值大,xml结构读取速度也不理想,这些问题都是有待解决的。需要解决这些问题,首先需要分析一下地图文件的结构,下图是一个普通的TiledMap地图数据文件。当然我这里没有用到TiledMap的碰撞等一些用法,就不分析了。


主要三个节点:

Map 地图的基本信息,包括地图大小,地图块大小,地图类型,绘制顺序等
Tileset 地图图片信息,图片的大小,单块图元的大小,图块编号,地形信息等
Layer 地图层的数据,主要记录每层地图上每个地图块对应的图元信息等

其中Layer层的数据是最大的。

文件格式选择

项目的目标平台是移动端,首选自然是binary文件,实现方式是将TiledMap的tmx文件解析出来,然后导出二进制文件,我这边使用的是TiledSharp来解析地图文件,然后将解析出来的文件使用BinaryWriter写入二进制文件。

数据结构分析

阅读TiledSharp相关代码后整理一下相关数据结构,发现针对我们这个项目的地图文件来说,主要的优化点就在数据文件中最大的那部分Layer层。

public class TmxLayer : ITmxElement {
...
   // Layer层中最大的数据就是`TmxLayerTile`集合,按我们的地图尺寸来算这个集合的大小是1201x1201
   public Collection<TmxLayerTile> Tiles {get; private set;}
...
}
public class TmxLayerTile {
...
   // TmxLayerTile内有下面这些数据
   public int Gid {get; private set;}
   public int X {get; private set;}
   public int Y {get; private set;}
   public bool HorizontalFlip {get; private set;}
   public bool VerticalFlip {get; private set;}
   public bool DiagonalFlip {get; private set;}
...
}

TmxLayerTile数据里有大量重复的图块信息数据,拿我们的地图文件来说,我们只有103个图块,而TmxLayer 中将这103个图块延伸出来的信息存储了1201x1201份,如果有多层则还要乘上层数。

优化存储

既然有大量的重复数据,那么思路就有了,我们可以加一个TmxLayerTile的数据容器,这个容器里存储所有地块用到的TmxLayerTile数据,TmxLayer里不再存储TmxLayerTile数据,只存储对应数据在数据容器里的索引即可。

/// <summary>
/// 数据容器
/// </summary>
public class TmxLayerTileData {
   // combine flip flag to one byte
   public byte Flags;
   public short Gid;

   public byte TilesetId;
   public bool HorizontalFlip {
       get {
           return (Flags & (1<<0)) != 0;
       }
   }
   
   public bool VerticalFlip {
       get {
           return (Flags & (1<<1)) != 0;
       }
   }

   public bool DiagonalFlip {
       get {
           return (Flags & (1<<2)) != 0;
       }
   }
   private static List<TmxLayerTileData> tmxLayerTileDatas = new List<TmxLayerTileData>();

   public override bool Equals(object obj) {
       return Gid == ((TmxLayerTileData)obj).Gid && Flags == ((TmxLayerTileData)obj).Flags && TilesetId == ((TmxLayerTileData)obj).TilesetId;
   }

   public override int GetHashCode() {
       return base.GetHashCode();
   }

   public static void AddData(TmxLayerTileData data) {
       if(tmxLayerTileDatas.Contains(data))
       {
           return;
       }
       tmxLayerTileDatas.Add(data);
   }

   public static ushort GetDataIndex(TmxLayerTileData data) {
       return (ushort)tmxLayerTileDatas.IndexOf(data);
   }

   public static TmxLayerTileData DataAt(int index) {
       if (index < 0 || index >= tmxLayerTileDatas.Count)
       {
           return null;
       }
       return tmxLayerTileDatas[index];
   }
}

相应的TmxLayer中不再需要TmxLayerTile集合,只需要一个byte数组记录对应的数据索引即可。

public class TmxLayer : ITmxElement {
...
   // Layer层中最大的数据就是`TmxLayerTile`集合,按我们的地图尺寸来算这个集合的大小是1201x1201
   [Obsolete("Don't use anymore")]
   public Collection<TmxLayerTile> Tiles {get; private set;}
   // 数据索引数组
   public byte[] Tiles {get; private set;}
...
}

修改完成后,导出地图文件大小仅2.76M,这2.76M的文件读取时不需要解压,没有base64解码,内存占用也很小,完全达到了我们的要求。

添加新评论