[GBA教程]pokeemerald三层地图块详解

[GBA教程]pokeemerald三层地图块详解

本文转自https://github.com/pret/pokeemerald/wiki/Triple-layer-metatiles

三层元图块

原版游戏使用了一种相当奇怪的方式来利用游戏的3个地图图层。为了完全控制这3个背景层,我们可以对游戏进行一些修改。

前提条件

  • Python >= 3.6

  • Porymap >= 4.3.0

原版行为

在原版游戏中,我们有以下几种在游戏主世界中使用背景层(BG)的模式:

  • 普通模式(使用 BG1 和 BG2)

  • 覆盖模式(使用 BG2 和 BG3)

  • 分割模式(使用 BG1 和 BG3)

当玩家位于主世界时,下表显示了每个图层的一些信息:

BG 图层 BG 优先级 对象事件高度(Z坐标) 内容
0 0 [13,14] 用户界面
1 1 [4,6,8,10,12] 顶部地图层
2 2 [1,2,3,5,7,9,11] 中部地图层
3 3 [] 底部地图层

如果NPC精灵对应的“高度”(也称为Z坐标)大于特定图层的优先级,则该精灵将渲染在该图层之上。这可能听起来有点令人困惑,这里举个例子:

玩家的Z坐标起始值为3(默认值),这意味着它将被顶部地图层以及用户界面所遮挡。一旦玩家切换到高度为4,它将被渲染在所有地图图层之上,但仍在用户界面之下。当玩家切换到高度为13时,它甚至会被渲染在用户界面之上。

  • 注意:高度0和15是特殊的。如果玩家踩到高度为0或15的图块上,他们将保持在之前离开时的高度(或者游戏在传送时设置的高度,该情况下是高度3)。玩家可以从高度为0的图块走到另一个高度的图块,因此高度0被用来从一个高度过渡到另一个高度。玩家不能从高度为15的图块走到一个与他们离开时不同的高度。高度15最常用于桥梁。

当你能够实际使用所有3个图层时,这个表格可能会派上用场。

编辑游戏代码

我们需要对游戏代码做的更改相当简单。首先,我们更改 include/fieldmap.h 中的 NUM_TILES_PER_METATILE 值:

-#define NUM_TILES_PER_METATILE 8
+#define NUM_TILES_PER_METATILE 12

如果你的项目版本较旧,没有这个常量,你需要将你的项目与最新的代码库进行比较,找到使用这个常量的地方,然后手动将你项目中出现的所有8改为12

基础功能

我们修改的第一个函数是 src/field_camera.c 中的 DrawMetatile,它负责将元图块渲染到VRAM。我们用以下代码覆盖该函数:

static void DrawMetatile(s32 metatileLayerType, const u16 *tiles, u16 offset)
{
    if (metatileLayerType == 0xFF)
    {
        // 需要绘制一个门元图块,我们使用覆盖行为
        // 将元图块的底层绘制到底部背景层(BG3)。
        gOverworldTilemapBuffer_Bg3[offset] = tiles[0];
        gOverworldTilemapBuffer_Bg3[offset + 1] = tiles[1];
        gOverworldTilemapBuffer_Bg3[offset + 0x20] = tiles[2];
        gOverworldTilemapBuffer_Bg3[offset + 0x21] = tiles[3];

        // 将透明图块绘制到顶部背景层(BG2)。
        gOverworldTilemapBuffer_Bg2[offset] = 0;
        gOverworldTilemapBuffer_Bg2[offset + 1] = 0;
        gOverworldTilemapBuffer_Bg2[offset + 0x20] = 0;
        gOverworldTilemapBuffer_Bg2[offset + 0x21] = 0;

        // 将元图块的顶层绘制到中部背景层(BG1)。
        gOverworldTilemapBuffer_Bg1[offset] = tiles[4];
        gOverworldTilemapBuffer_Bg1[offset + 1] = tiles[5];
        gOverworldTilemapBuffer_Bg1[offset + 0x20] = tiles[6];
        gOverworldTilemapBuffer_Bg1[offset + 0x21] = tiles[7];

    }
    else
    {
        // 将元图块的底层绘制到底部背景层(BG3)。
        gOverworldTilemapBuffer_Bg3[offset] = tiles[0];
        gOverworldTilemapBuffer_Bg3[offset + 1] = tiles[1];
        gOverworldTilemapBuffer_Bg3[offset + 0x20] = tiles[2];
        gOverworldTilemapBuffer_Bg3[offset + 0x21] = tiles[3];

        // 将元图块的中层绘制到中部背景层(BG2)。
        gOverworldTilemapBuffer_Bg2[offset] = tiles[4];
        gOverworldTilemapBuffer_Bg2[offset + 1] = tiles[5];
        gOverworldTilemapBuffer_Bg2[offset + 0x20] = tiles[6];
        gOverworldTilemapBuffer_Bg2[offset + 0x21] = tiles[7];

        // 将元图块的顶层绘制到顶部背景层(BG1),该层会覆盖对象事件精灵。
        gOverworldTilemapBuffer_Bg1[offset] = tiles[8];
        gOverworldTilemapBuffer_Bg1[offset + 1] = tiles[9];
        gOverworldTilemapBuffer_Bg1[offset + 0x20] = tiles[10];
        gOverworldTilemapBuffer_Bg1[offset + 0x21] = tiles[11];

    }

    ScheduleBgCopyTilemapToVram(1);
    ScheduleBgCopyTilemapToVram(2);
    ScheduleBgCopyTilemapToVram(3);
}

修复门的问题

以目前的状态,门会出问题。绘制门也会调用 DrawMetatile,但提供的包含门动画图块的数组对于我们的新三层系统来说太小了。为了解决这个问题,我们已经在 DrawMetatile 中做了一个例外处理(见上文),并且需要相应地更改 DrawDoorMetatileAt

- DrawMetatile(METATILE_LAYER_TYPE_COVERED, tiles, offset);
+ DrawMetatile(0xFF, tiles, offset);

这使得游戏在处理门动画时使用正常的渲染行为。

修复友好商店

商店在原版中很奇怪。它们试图将图块从BG1移动到其他2个BG,以便为“pokemart”用户界面腾出空间。它们还会重新绘制大部分地图,这需要更新。所有这些更改都在 src/shop.c 文件中。

在 BuyMenuDrawMapBg 函数中:

for (i = 0; i < 15; i++)
         {
             metatile = MapGridGetMetatileIdAt(x + i, y + j);
             if (BuyMenuCheckForOverlapWithMenuBg(i, j) == TRUE)
-                metatileLayerType = MapGridGetMetatileLayerTypeAt(x + i, y + j);
+                metatileLayerType = METATILE_LAYER_TYPE_NORMAL;
             else
                 metatileLayerType = METATILE_LAYER_TYPE_COVERED;

这将使元图块的大小正确,并且还会更新 metatileLayerType,我们稍后将用它来进行一些图块重新排序。接下来,看一下 BuyMenuDrawMapMetatile 函数:

static void BuyMenuDrawMapMetatile(s16 x, s16 y, const u16 *src, u8 metatileLayerType)
 {
     u16 offset1 = x * 2;
     u16 offset2 = y * 64;
-
-    switch (metatileLayerType)
+    if (metatileLayerType == METATILE_LAYER_TYPE_NORMAL)
     {
-    case METATILE_LAYER_TYPE_NORMAL:
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src);
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[1], offset1, offset2, src + 4);
-        break;
-    case METATILE_LAYER_TYPE_COVERED:
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
+        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src + 0);
         BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 4);
-        break;
-    case METATILE_LAYER_TYPE_SPLIT:
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
-        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[1], offset1, offset2, src + 4);
-        break;
+        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[1], offset1, offset2, src + 8);
+    }
+    else
+    {
+        if (IsMetatileLayerEmpty(src))
+        {
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src + 4);
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 8);
+        }
+        else if (IsMetatileLayerEmpty(src + 4))
+        {
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 8);
+        }
+        else if (IsMetatileLayerEmpty(src + 8))
+        {
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
+            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 4);
+        }
     }
 }

或者,直接复制并粘贴这个函数:

static void BuyMenuDrawMapMetatile(s16 x, s16 y, const u16 *src, u8 metatileLayerType)
{
    u16 offset1 = x * 2;
    u16 offset2 = y * 64;
    if (metatileLayerType == METATILE_LAYER_TYPE_NORMAL)
    {
        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src + 0);
         BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 4);
        BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[1], offset1, offset2, src + 8);
    }
    else
    {
        if (IsMetatileLayerEmpty(src))
        {
            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src + 4);
            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 8);
        }
        else if (IsMetatileLayerEmpty(src + 4))
        {
            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 8);
        }
        else if (IsMetatileLayerEmpty(src + 8))
        {
            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
            BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 4);
        }
    }
}

这将处理三层图块的绘制,除非地图网格上的元素会与用户界面元素重叠。在这种情况下,它会尝试找到一个空层并相应地移动其他图块。

你还需要在 BuyMenuDrawMapMetatile 函数上方某处添加这个函数:

static bool8 IsMetatileLayerEmpty(const u16 *src)
{
    u32 i = 0;
    for (i = 0; i < 4; ++i)
    {
        if ((src[i] & 0x3FF) != 0)
            return FALSE;
    }
    return TRUE;
}

请注意,在使用友好商店时,当商店打开时,你必须确保商店用户界面元素周围没有三层图块。商店本身占用了一个BG层,这是我们在这里需要考虑的。

更新现有的图块集

如前所述,此方法要求每个元图块有4个额外的图块映射条目。普通的图块集数据不包含这些数据,在此阶段你的游戏看起来会是损坏的。幸运的是,我们可以运行一个简单的Python脚本来迁移旧的图块集。可以在这里找到:https://gist.github.com/SBird1337/ccfa47b5ef41c454b637735d4574592a

下载后,使用 python3 运行它。它期望你的 data/tilesets 目录路径作为 tsroot 参数。你可以像这样运行:

python3 triple_layer_converter.py --tsroot <path/to/pokeemerald/data/tilesets>

例如,如果我的 pokeemerald 实例在 /home/hacker/pokeemerald 目录下,我会运行:

python3 triple_layer_converter.py --tsroot /home/hacker/pokeemerald/data/tilesets

脚本将为每个成功转换的图块集输出 [OK]

使用 Porymap

幸运的是,porymap 在视觉和功能上都支持这个新系统。

  • 如果你使用的 porymap 版本 >= 6.0.0,无需任何额外操作!Porymap 会自动为你启用必要的设置。如果 porymap 已经打开,请确保重新加载你的项目。

  • 如果你使用的 porymap 版本 >= 5.2.0,请转到 Options -> Project Settings...,然后在 Tilesets 标签下勾选 Enable Triple Layer Metatiles 选项。然后选择 OK 并重新加载你的项目。

  • 如果你使用的是旧版本的 porymap (< 5.2.0),你必须在你的 pokeemerald 目录下的 porymap.project.cfg 文件中手动将 enable_triple_layer_metatiles 设置为 1

差不多就是这样了,你现在可以在 porymap 中使用三层支持。请注意,在图块集编辑器中会出现第三层,并且 Layer Type 属性消失了(不再需要它了)。

 

© 版权声明
THE END
喜欢就支持一下吧
点赞1 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容