整体地图设计与图块阵列设计


11

我正在开发2D RPG,它将具有通常的地牢/城镇地图(预先生成)。

我正在使用图块,然后将它们结合起来制作地图。我最初的计划是使用Photoshop或其他一些图形程序组装瓷砖,以便获得一张更大的图片,然后将其用作地图。

但是,我在一些地方读过人们在谈论他们如何使用数组在引擎中构建地图(因此,您向引擎提供了x个tile数组,并将它们组装成地图)。我能理解它是如何完成的,但是实现起来似乎要复杂得多,而且看不到明显的优点。

最常用的方法是什么,每种方法的优点/缺点是什么?


1
主要问题是内存。一次填充1080p屏幕的纹理约为60mb。因此,图像每像素使用一个整数,图块每像素使用一个整数(像素/(平铺*切片大小))。因此,如果图块为32,32,则可以通过使用图块与像素来表示1024倍的地图。同样,纹理交换时间也将不利于推动那么多内存等等
ClassicThunder 2012年

Answers:


19

首先,我要说的是2D RPG几乎是我亲爱的,并且与旧的DX7 VB6 MORPG引擎一起工作(别笑了,那是8年前了,现在:-))是首先让我对游戏开发产生兴趣的。最近,我开始将在其中一个引擎中开发的游戏转换为使用XNA。

就是说,我的建议是对地图使用分层的基于图块的结构。使用您使用的任何图形API,都会限制可以加载的纹理大小。更不用说显卡纹理内存限制。因此,考虑到这一点,如果您要最大化地图的大小,同时不仅要最小化加载到内存中的纹理的数量和大小,还要减少用户硬盘驱动器上的资产大小和加载时间,肯定会想与瓷砖。

就实现而言,我已经在GameDev.SE和我的博客(均在下面链接)中的一些问题上详细介绍了如何处理它,这与您的要求不完全相同,因此我只是在这里介绍基础知识。我还将记下磁贴的功能,这些磁贴使它们比加载多个大型预渲染图像更有利。如果不清楚,请告诉我。

  1. 您需要做的第一件事是创建一个磁贴纸。这只是一张大图,其中包含您在网格中对齐的所有图块。这(可能需要额外的一个,具体取决于切片的数量)将是您唯一需要加载的内容。只需1张图片!您可以在每个地图中加载1张,或者在游戏中的每个图块加载1张;任何组织为您工作。
  2. 接下来,您需要了解如何使用该“工作表”并将每个磁贴转换为数字。通过一些简单的数学运算,这非常简单。请注意,这里的除法是整数除法,因此小数点后的位置将舍去(或根据需要四舍五入)。 如何将单元格转换为坐标并返回。
  3. 好的,既然您已经将tilesheet分成了一系列单元格(数字),则可以将这些数字插入到您喜欢的任何容器中。为了简单起见,您只能使用2D阵列。

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. 接下来,您要绘制它们。您可以使这批处理更有效(取决于地图大小)的方法之一是仅计算摄像机当前正在查看的单元格,并在这些单元格之间循环。您可以通过获取相机左上角(tl)和右下角()的地图图块阵列坐标来实现br。然后从tl.X to br.X和循环,tl.Y to br.Y以嵌套循环的方式绘制它们。下面的示例代码:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
    
  5. 大奖!这就是平铺引擎的基础。您可以看到,即使没有太多的开销,即使是1000x1000的地图也很容易。另外,如果少于255个图块,则可以使用字节数组将每个单元的内存减少3个字节。如果字节太小,则满足您的需求的ushort可能就足够了。

注意:我忽略了世界坐标的概念(这是您相机的位置所基于的概念),因为我认为这不在此答案的范围内。您可以在GameDev.SE上阅读有关内容。

我的Tile-Engine资源
注意:所有这些都是针对XNA的,但是它几乎适用于所有内容-您只需要更改绘图调用即可。

  • 我对这个问题的回答概述了我如何处理游戏中的地图单元和分层。(请参阅第三个链接。)
  • 我对这个问题的回答说明了如何以二进制格式存储数据。
  • 是关于我正在开发的游戏的第一篇博客(技术上来说是第二篇,但第一篇是“技术”)。整个系列包含有关像素着色器,照明,瓷砖行为,运动以及所有有趣的东西的信息。我实际上更新了该帖子,以包含我在上面发布的第一个链接的答案的内容,因此您可能希望阅读它(我可能已经添加了一些内容)。如果您有任何疑问,可以在此处或此处发表评论,我们很乐意为您提供帮助。

其他平铺引擎资源

  • 从瓦引擎教程这个网站给了我用来创建我的地图的基础。
  • 我实际上还没有看过这些视频教程,因为我没有时间,但是它们可能会有所帮助。:)如果您使用的是XNA,它们可能已经过时了。
  • 网站还有一些基于上述视频的教程(我认为)。可能值得一试。

哇,这比我问的要多两倍的信息,几乎可以回答我没问过的所有问题,太好了,谢谢:)
Cristol.GdM 2012年

@Mikalichov-很高兴我能帮上忙!:)
理查德·马斯克

通常,对于2D数组,您想将第一个维用作Y,将第二个维用作X。在内存中,数组将依次为0,0-i,然后为1,0-i。如果先用X做嵌套循环,则实际上是在内存中来回跳跃,而不是遵循顺序的读取路径。始终为(y),然后为(x)。
Brett W

@BrettW-有效点。谢谢。我想知道二维数组如何存储在.Net中(行主要顺序。今天学到了一些新东西!:-))。但是,更新代码后,我意识到它与您所描述的完全相同,只是切换了变量。区别在于以什么顺序绘制瓷砖。我当前的示例代码从上到下,从左到右绘制,而您描述的内容将从左到右,从上到下绘制。因此,为简单起见,我决定将其还原为原始格式。:-)
理查德·马斯克

6

基于图块的系统和静态模型/纹理系统都可以用来表示一个世界,并且每个都有不同的优势。一个人是否比另一个人更好,可以归结为您如何使用这些组件,以及哪种组件最适合您的技能和需求。

话虽如此,两个系统都可以单独使用,也可以一起使用。

基于标准图块的系统具有以下优点:

  • 简单的2D瓷砖阵列
  • 每个瓷砖只有一种材料
    • 该材料可以是单个纹理,也可以是多个纹理,也可以是周围瓷砖的混合材料
    • 可以为该类型的所有图块重复使用纹理,从而减少资产创建和返工
  • 每个图块是否可以通过(冲突检测)
  • 易于渲染和识别地图的各个部分
    • 角色位置和地图位置是绝对坐标
    • 轻松遍历屏幕区域的相关图块

切片的缺点是您必须创建一个用于构建基于切片的数据的系统。您可以创建一个将每个像素用作图块的图像,从而从纹理创建2D阵列。您也可以创建专有格式。使用位标志,您可以在相当小的空间中为每个图块存储大量数据。

大多数人做图块的主要原因是因为它允许他们创建小资产并重新使用它们。这使您可以创建更大的图片,并将较小的片段。因为您不需要更改整个世界地图以进行小的更改,所以可以减少返工。例如,如果您想更改所有草的阴影。在大图中,您将必须重新粉刷所有草皮。在图块系统中,您只需更新草砖。

总而言之,与大型图形地图相比,使用基于图块的系统进行的返工可能要少得多。即使不将其用于地面地图,也可能最终使用基于图块的系统进行碰撞。仅仅因为您在内部使用了图块并不意味着您就不能使用模型来表示环境对象,该对象可能为其空间使用多个图块。

您需要提供有关环境/引擎/语言的更多细节,以提供代码示例。


谢谢!我正在C#下工作,并且打算使用类似(但人才较少)的Final Fantasy 6(或基本上是大多数Square SNES RPG)版本,因此使用地砖和结构砖(即房屋)。我最大的担心是,如果不花大量时间检查“那里有一个草角,然后是三个笔直的水平,然后是一个房屋角,然后..”,那么我将看不到如何构建2D阵列,以及沿途的大量错误。道路。
Cristol.GdM 2012年

理查德(Richard)似乎已经在代码方面进行了介绍。我个人将使用枚举类型的图块,并使用位标志来标识图块的值。这使您可以平均存储32个以上的值。您还将能够为每个图块存储多个标志。因此,您可以说Grass = true,Wall = true,Collision = true等,而无需单独的变量。然后,您只需分配“主题”即可确定该特定地图区域的图形图块。
Brett W

3

绘制地图:图块在引擎上很容易,因为这样地图就可以表示为巨大的图块索引数组。绘制代码只是一个嵌套循环:

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

对于大型地图,整个地图的一个巨大的位图图像可能会占用大量内存,并且可能大于图形卡支持的单个图像大小。但是您不会对图块产生任何问题,因为您只需为每个图块分配一次图形内存(或者,如果它们全部都在一张纸上,则最好分配一个总计)

重用图块信息进行冲突也很容易。例如,要获得角色小精灵左上角的地形图块:

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

假设您需要检查是否与岩石/树木等发生了碰撞。您可以在(字符位置+字符精灵大小+当前方向)的位置获取图块,并检查其是否标记为可行走或不可行走。

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.