什么是存储tilemap数据的好方法?


13

我正在与一些uni朋友一起开发2D平台游戏。我们基于XNA Platformer入门工具包,该工具包使用.txt文件存储切片地图。尽管这很简单,但是它在关卡设计中并没有给我们足够的控制和灵活性。例如:对于多层内容,需要多个文件,每个对象都固定在网格上,不允许旋转对象,限制字符数等。因此,我正在研究如何存储关卡数据和地图文件。

这仅涉及切片图的文件系统存储,而不涉及游戏运行时要使用的数据结构。瓦片贴图已加载到2D数组中,因此此问题与填充数组的源有关。

DB的推理:从我的角度来看,我发现使用数据库存储切片数据的数据冗余较少。具有相同特征的相同x,y位置的图块可以在各个级别之间重复使用。似乎很容易编写一种方法来从数据库中检索特定级别中使用的所有切片。

JSON / XML的推理:视觉上可编辑的文件,可以通过SVN轻松跟踪更改。但是有重复的内容。

与另一个相比,它们是否有任何缺点(加载时间,访问时间,内存等)?以及行业中常用的是什么?

当前文件如下所示:

....................
....................
....................
....................
....................
....................
....................
.........GGG........
.........###........
....................
....GGG.......GGG...
....###.......###...
....................
.1................X.
####################

1-玩家起点,X级出口,。-空的空间,#-平台,G-宝石


2
您正在使用什么现有的“格式”?仅仅说“文本文件”就意味着您不保存二进制数据。当你说“没有足够的控制和灵活性”,是什么,特别是你在运行的问题?为什么要在XML和SQLite之间折腾?那将决定答案。 blog.stackoverflow.com/2011/08/gorilla-vs-shark
四分

我会选择JSON,因为它更具可读性。

3
人们为什么考虑将SQLite用于这些用途?这是一个关系数据库;人们为什么认为关系数据库可以形成良好的文件格式?
Nicol Bolas

1
@StephenTierney:为什么这个问题突然从XML与SQLite变为JSON与任何数据库?我的问题是专门为什么不只是“存储磁贴图数据的好方法是什么?” X与Y的问题是武断且毫无意义的。
Nicol Bolas

1
@Kylotan:但是效果不是很好。由于您只能通过SQL命令编辑数据库,因此难以编辑。很难阅读,因为表格并不是查看磁贴图并了解正在发生的事情的有效方法。尽管它可以进行查找,但是查找过程非常复杂。使某些东西正常工作很重要,但是如果这会使真正开发游戏的过程变得困难得多,那么走短路就不值得了。
Nicol Bolas

Answers:


14

因此,通读更新后的问题,您似乎最担心磁盘上的“冗余数据”,还有一些有关加载时间和行业用途的担忧。

首先,您为什么担心冗余数据?即使对于像XML这样的过大的格式,实现压缩并缩小级别的规模也将是微不足道的。与关卡数据相比,您更可能占用纹理或声音的空间。

其次,二进制格式比必须解析的基于文本的格式更有可能更快地加载。令人怀疑的是,如果您可以将其放在内存中并在其中放置数据结构。但是,这有一些缺点。首先,二进制文件几乎无法调试(尤其是对于内容创建人员而言)。您不能对它们进行差异化或版本化。但是它们会更小,并且加载速度更快。

一些引擎的工作(以及我认为理想的情况)是实现两个加载路径。为了进行开发,您使用某种基于文本的格式。只要使用可靠的库,实际上使用哪种特定格式都没有关系。对于发行版,您切换到加载二进制(更小,更快的加载,可能剥离调试实体)版本。您用于编辑关卡的工具会同时吐出两个。这不仅是一种文件格式,还需要做很多工作,但是您可以同时兼顾两者。

话虽这么说,我想你在跳枪。

解决这些问题的第一步是始终彻底描述您所遇到的问题。如果知道需要什么数据,那么就知道需要存储什么数据。从那里有一个可测试的优化问题。

如果您有用例进行测试,则可以测试几种不同的存储/加载数据方法,以衡量您认为重要的事情(加载时间,内存使用量,磁盘大小等)。不知道这些,就很难说得更具体。


9
  • XML:易于手动编辑,但是一旦开始加载大量数据,它就会变慢。
  • SQLite:如果您想一次检索很多数据,甚至只是一小部分,这可能会更好。但是,除非您在游戏中的其他地方使用此功能,否则我认为这对于您的地图来说是过大的(并且可能过于复杂)。

我的建议是使用自定义二进制文件格式。在我的游戏中,我只有一种SaveMap方法可以使用并保存每个字段BinaryWriter。这也使您可以根据需要选择压缩它,并更好地控制文件大小。即节省short代替int,如果你知道它不会比32767大。在一个大循环,节省了一些作为short,而不是一个int可以意味着很多较小的文件大小。

另外,如果您采用这种方式,建议您在文件中的第一个变量为版本号。

例如,考虑一个地图类(非常简化):

class Map {
    private const short MapVersion = 1;
    public string Name { get; set; }

    public void SaveMap(string filename) {
        //set up binary writer
        bw.Write(MapVersion);
        bw.Write(Name);
        //close/dispose binary writer
    }
    public void LoadMap(string filename) {
        //set up binary reader
        short mapVersion = br.ReadInt16();
        Name = br.ReadString();
        //close/dispose binary reader
    }
}

现在,让我们说,你想一个新的属性添加到您的地图,说ListPlatform对象。我之所以选择它,是因为它涉及更多。

首先,您增加MapVersion并添加List

private const short MapVersion = 2;
public string Name { get; set; }
public List<Platform> Platforms { get; set; }

然后,更新保存方法:

public void SaveMap(string filename) {
    //set up binary writer
    bw.Write(MapVersion);
    bw.Write(Name);
    //Save the count for loading later
    bw.Write(Platforms.Count);
    foreach(Platform plat in Platforms) {
        //For simplicities sake, I assume Platform has it's own
        // method to write itself to a file.
        plat.Write(bw);
    }
    //close/dispose binary writer
}

然后,这就是您真正看到好处的地方,更新load方法:

public void LoadMap(string filename) {
    //set up binary reader
    short mapVersion = br.ReadInt16();
    Name = br.ReadString();
    //Create our platforms list
    Platforms = new List<Platform>();
    if (mapVersion >= 2) {
        //Version is OK, let's load the Platforms
        int mapCount = br.ReadInt32();
        for (int i = 0; i < mapCount; i++) {
            //Again, I'm going to assume platform has a static Read
            //  method that returns a Platform object
            Platforms.Add(Platform.Read(br));
        }
    } else {
        //If it's an older version, give it a default value
        Platforms.Add(new Platform());
    }
    //close/dispose binary reader
}

如您所见,该LoadMaps方法不仅可以加载地图的最新版本,还可以加载旧版本!当它加载较旧的地图时,您可以控制它使用的默认值。


9

短篇故事

一个解决此问题的好方法是将您的级别存储在位图中,每个图块一个像素。使用RGBA,可以轻松地将四个不同的维度(图层,id,旋转度,色彩等)存储在单个图像上。

很长的故事

这个问题使我想起了Notch在几个月前直播他的Ludum Dare条目时的情况,我想分享一下,以防您不知道他做了什么。我认为这真的很有趣。

基本上,他使用位图存储其关卡。位图中的每个像素对应于世界上的一个“平铺”(在他的情况下,这并不是真正的平铺,因为它是一个光线投射的游戏,但足够接近)。他的级别之一的示例:

在此处输入图片说明

因此,如果您使用RGBA(每通道8位),则可以将每个通道用作不同的图层,并且每个图层最多可以使用256种类型的图块。够了吗?或者,例如,一个通道可以像您提到的那样保持图块的旋转。此外,创建一个使用这种格式的关卡编辑器也应该很简单。

最好的是,您甚至不需要任何自定义内容处理器,因为您可以像将其加载到XNA一样,将其像任何其他Texture2D一样,并循环读取像素。

IIRC他在地图上使用了一个纯红色的点来向敌人发出信号,并根据该像素的alpha通道值确定敌人的类型(例如,alpha值255可能是蝙蝠,而254则可能是蝙蝠。僵尸或其他东西)。

另一个想法是将游戏对象细分为固定在网格中的对象和可以在图块之间“精细”移动的对象。将固定的图块保留在位图中,将动态对象保留在列表中。

甚至可能有一种方法可以将更多信息复用到这4个通道中。如果有人对此有任何想法,请告诉我。:)


3

您几乎可以逃脱一切。不幸的是,现代计算机是如此之快,以至于即使这些数据以in肿且结构不良的数据格式存储,也需要大量的处理才能读取,但这种相对少量的数据实际上并不会产生明显的时差。

如果您想做“正确的”事情,可以像Drackir建议的那样制作二进制格式,但是如果出于某种愚蠢的原因做出其他选择,它可能不会再出现并咬住您(至少在性能方面不是这样)。


1
是的,这绝对取决于大小。我有一张大约161000瓷砖的地图。每个都有6层。我最初是用XML编写的,但是它只有82MB,花了很多时间才能加载。现在,我将其保存为二进制格式,压缩之前大约为5mb,加载大约为200ms。:)
理查德·马斯克

2

请参阅以下问题:游戏数据的“二进制XML”?我还会选择JSON或YAML甚至XML,但这会产生很多开销。至于SQLite,我绝不会将其用于存储级别。如果您希望存储大量相关数据(例如,具有关系链接/外键),并且如果您希望询问大量不同的查询,那么关系数据库将很方便,存储tilemap似乎并不会执行这些操作东西,会增加不必要的复杂性。


2

这个问题的根本问题是,它融合了两个彼此无关的概念:

  1. 文件存储范例
  2. 数据的内存中表示

您可以将文件存储为纯文本格式,可以使用任意格式,JSON,Lua脚本,XML,任意二进制格式等。这些都不要求您的数据在内存中采用任何特定格式。

关卡加载代码的工作就是将文件存储模式转换为内存中的表示形式。例如,您说:“使用数据库存储切片数据,我看到的数据冗余较少。” 如果您希望减少冗余,那是您的关卡加载代码应该考虑的事情。这就是您的内存表示形式应该能够处理的东西。

这是不是东西去关注你的文件格式的需求。这就是为什么:这些文件必须来自某个地方。

您将要手工编写这些内容,或者将要使用某种工具来创建和编辑级别数据。如果要手写它们,那么您需要的最重要的是一种易于阅读和修改的格式。数据冗余是不是您的格式,甚至需要考虑,因为你会花大部分时间编辑文件。您是否真的要手动使用提供的数据冗余机制?仅仅由您的水平加载器来处理它,这会更好地利用您的时间吗?

如果您有创建它们的工具,则实际格式(至多)仅出于可读性考虑(至多)。您可以在这些文件中放入任何内容。如果要忽略文件中的冗余数据,只需设计一种可以做到的格式,然后使您的工具正确使用该功能。您的tilemap格式可以包括RLE(行程编码)压缩,重复压缩以及所需的压缩技术,因为这是您的 tilemap格式。

问题解决了。

存在关系数据库(RDB)来解决对包含许多数据字段的大型数据集执行复杂搜索的问题。您在图块地图中所做的唯一一种搜索是“在X,Y位置获取图块”。任何数组都可以处理。使用关系数据库来存储和维护图块地图数据将是极端过大的选择,实际上并不值得。

即使您在内存表示形式中进行了一些压缩,您仍然可以在性能和内存占用方面轻松击败RDB。是的,您将必须实际实施该压缩。但是,如果您担心内存中的数据大小,那么可以考虑使用RDB,那么您实际上可能希望实现特定类型的压缩。您将比RDB更快,同时内存更少。


1
  • XML:确实很容易使用,但是当结构深入时,开销变得很烦人。
  • SQLite:对于大型结构更方便。

对于非常简单的数据,如果您已经熟悉XML加载和解析的话,我可能会选择XML路由。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.