在本教程系列的开头,我提到过这款游戏的灵感来源于1981年版本的《大金刚》。我再次提起这一点,是因为当我研究原版游戏以分析其机制和玩法时,发现原版《大金刚》只有四个关卡。四个关卡与当今大多数游戏相比似乎微不足道——我的意思是,我们可能都认识某个在《糖果粉碎传奇》中玩到506关的人。
1.png
图7:经典《大金刚》的四个关卡
在当时,程序化生成关卡并不常见。程序化生成是一种通过算法生成关卡或内容的技术,而不是手动设计,这种技术在许多视频游戏中都有应用,尤其是在类Rogue游戏和开放世界游戏中。然而,原版《大金刚》及其早期续作主要依赖于手工设计的关卡。这意味着开发者自己创建了关卡,这也解释了为什么《大金刚》只有四个关卡。

在Godot 4中创建一个程序化生成的《大金刚》风格关卡,意味着要通过算法生成平台、梯子和其他元素,而不是手动放置它们。由于这是一个初学者教程,我们今天不会深入探讨程序化生成的复杂性,而是保持传统,手动创建四个关卡。

除了关于高级库存、任务和对话系统的完整教程系列外,我还在制作一个专注于程序化生成世界和地图的独立系列。这些教程将在今年晚些时候免费提供。

在本部分中,你将学习:

  • 如何创建TileMap资源。
  • 如何添加物理和地形层。
  • 如何更改TileSet属性。

让我们分解一个《大金刚》关卡的基础,以确定我们的关卡需要哪些对象:
2.png
目前,我们只专注于添加平台和梯子。我们需要在创建每个关卡时考虑起点和终点。我建议你阅读MakeCode的这篇文章,它为你提供了平台游戏关卡设计基础的概述。

让我们从基础开始,即在游戏屏幕上绘制游戏边框(城堡的外墙)。我们将使用TileMap节点来实现这一点。TileMap是用于创建游戏布局的网格。要使用TileMap,首先需要创建一个TileSet。TileSet是可以放置在TileMap节点中的图块集合。

简单来说,TileMap是我们用来创建地图或环境的完整图块集合。例如:
4.png
TileSet是将TileMap分解为小立方体的资源,然后我们将这些小立方体视为独立对象。例如:
3.png
让我们在主场景中添加一个新的TileMap节点,并将其重命名为“Level”。
5.png
6.png
要使用这个TileMap,我们需要为其分配一个TileSet资源。我们可以在选择TileMap节点后,在检查器面板中分配一个新的TileSet资源。我们需要在Tile Set属性旁边的<空>块中分配一个“新建TileSet”。
7.png
这将在编辑器下方打开两个新的资源块:Tilemap和Tileset。我们将使用Tilemap面板将TileSet绘制到屏幕上,并使用Tileset面板为Tilemap面板分配TileSheet以进行绘制。我们还可以在Tileset面板中更改单个图块的属性。
9.png
让我们为Tilemap分配一个新的TileSheet,以便绘制城堡边框。为此,点击Tileset面板,然后在文件系统管理器面板中将“res://Assets/Kings and Pigs/Sprites/14-TileSets/Terrain (32x32).png” TileSheet拖到Tiles块中。你可以根据需要拖入任意数量的TileSheet,因为我们将使用许多TileSheet来创建我们的世界。
8.png
会弹出一个窗口,询问你是否要创建网格。选择“是”,因为这将把我们的TileSheet分解为可管理的小块。
12.png
现在我们可以回到Tilemap面板,你会看到TileSheet已经可用。你可以使用上面的工具选择、绘制和擦除图块。如果你选择一个图块并将其绘制到屏幕上,它应该会被绘制为一个新对象!
10.png
以下是Tilemap面板中可用的绘制工具概述:
11.png
14.png
Tilemap碰撞
在开始绘制边框之前,我们首先需要为地板和墙壁图块分配物理层。这将为每个图块提供碰撞形状。为此,选择TileMap节点,点击检查器中的TileSet属性值进行编辑,然后展开物理层并选择添加元素。现在我们可以回到Tileset面板为图块配置碰撞。
13.png
以下是Tileset面板中可用的工具概述:
16.png
18.png
我们的TileSet只包含立方体形状的物品——因为它只有地板和墙壁。因此,我们可以使用绘制模式面板批量添加碰撞。如果我们有不同形状的图块,比如一棵树,我们将在选择模式面板中为其添加碰撞,因为我们需要手动绘制其形状。

我们可以在选择和绘制模式面板中的物理层属性下绘制单个碰撞形状。如果你选择一个图块并导航到其物理层属性,你可以在红色轮廓的块中绘制形状。在这里,你将绘制出物品的形状。
15.png

正如我之前提到的,我们的形状是简单的正方形,因此我们可以在绘制模式面板中加快这一过程。选择“绘制”面板并导航到物理层属性。你仍然可以像在设置模式面板中一样绘制形状,但在这里我们可以批量添加这个形状到多个图块。让我们不要改变这个形状,因为我们只需要一个立方体。如果你点击一个图块,蓝色框会勾勒出它,这意味着碰撞形状已经从框中添加到图块中!
17.png
*记住不要为你希望玩家穿过的图块或用作背景的图块添加碰撞——因为它会阻挡玩家。

按如下方式绘制碰撞:
20.png
Tilemap地形
现在我们已经为墙壁边框添加了碰撞,我们需要继续绘制它们。我们可以手动绘制每个图块,或者通过使用自动图块来加快这一过程。Godot提供了地形功能,可以自动为墙壁、地板或地形等图块创建连接,这样我们就不必手动绘制图块的角落和边缘。这允许你自动使用“正确”的图块变体。
19.png
图7:地形与无地形
要添加地形,选择TileMap节点,进入检查器并选择你的TileSet资源。然后添加一个新的地形层,这将创建一个需要地形元素的地形集。地形集可以是类似“草地”的东西,而地形元素可以是类似“草地_森林”的东西。该集合将包含同一类别的所有元素。
21.png
让我们将地形元素重命名为“平台”,并将其颜色更改为易于识别的颜色。
22.png
现在如果我们进入Tileset面板,在设置或绘制模式下,我们可以看到地形属性现在可用。在选择模式面板中,你会看到我们的地形集和地形都有ID值。-1表示“无地形集”或“无地形”,这意味着你必须将地形集设置为0或更大,然后才能将地形设置为0或更大。在我们的情况下,地形集“0”将包含我们所有的平台,地形“0”是我们的“平台”地形元素。
23.png
然后你可以为其分配位对等值。对等位决定了将放置哪个图块,具体取决于相邻图块。例如,如果一个图块的所有位都设置为0或更大,则只有当所有8个相邻图块都使用具有相同地形ID的图块时,它才会出现。如果一个图块的位设置为0或更大,但左上、上和右上位设置为-1,则只有当其上方(包括对角线)有空间时,它才会出现。
25.png
我们不会在选择模式面板中分配位对等值,而是会在绘制模式面板中将其绘制到地图上。你可以在绘制模式面板中的地形属性中直接选择地形元素并进行设置,而无需提供ID。让我们选择我们的平台元素。
24.png
现在你可以通过点击图块来绘制位对等值。记住不要在明显的角落或尖锐边缘上绘制位对等值,因为这在绘制地形时会创建奇怪的形状。

按如下方式绘制平台地形:
27.jpg
如果你现在回到Tilemap面板,在地形下,你可以看到平台元素的“自动图块”已经创建。如果你选择平台元素并在地图上绘制,形状应该会自动创建并匹配!
28.jpg
让我们在地形集中创建两个新的地形元素,分别称为“Platform_Wall”和“Platform_Floor”。
29.jpg
按如下方式绘制Platform_Wall地形:
30.jpg
按如下方式绘制Platform_Floor地形:
31.jpg
如果你现在回到Tilemap面板,在地形下,你的新地形应该已经创建。
32.jpg
33.jpg
让我们使用这些地形在游戏屏幕边缘绘制一个边框——即蓝色边框。确保你的玩家保持在这个边框内。
34.jpg
35.jpg
在测试之前,我们首先需要修复玩家的碰撞形状。如果你在调试>可见碰撞形状选项中启用碰撞,并运行场景,你会注意到如果玩家向左或向右跑(由于动画帧中的空间),碰撞形状会远离玩家。这是一个问题,因为如果障碍物击中我们的碰撞形状,即使技术上没有击中我们,玩家也会在之后失去生命!
36.jpg
37.jpg
38.jpg
我们可以通过将碰撞形状居中到AnimatedSprite2D节点来解决这个问题。让我们将CollisionShape2D节点的x位置偏移量更改为-7。这将在游戏开始时使形状居中。在检查器面板中,在变换>位置下,将x值更改为-7,这将使形状居中。还要确保矩形的形状覆盖玩家的整个身体。
39.jpg
现在,在我们的代码中,我们需要在玩家改变方向时保持这个形状居中——因为动画有空间偏移。

Player.gd

# 旧代码

# 动画
func player_animations():
    # 向左(添加 is_action_just_released,以便在跳跃后继续奔跑)
    if Input.is_action_pressed("ui_left") || Input.is_action_just_released("ui_jump"):
        $AnimatedSprite2D.flip_h = true
        $AnimatedSprite2D.play("run")
        $CollisionShape2D.position.x = 7
        
    # 向右(添加 is_action_just_released,以便在跳跃后继续奔跑)
    if Input.is_action_pressed("ui_right") || Input.is_action_just_released("ui_jump"):
        $AnimatedSprite2D.flip_h = false
        $AnimatedSprite2D.play("run")
        $CollisionShape2D.position.x = -7
    
    # 如果没有按下任何键,则处于空闲状态
    if !Input.is_anything_pressed():
        $AnimatedSprite2D.play("idle")

你的代码应该看起来像这样。

如果我们现在运行场景,边框应该会阻止玩家掉落,因为我们为其添加了碰撞,我们终于可以四处跳跃了!我们的碰撞形状现在也应该保持与玩家位置居中。
40.jpg
41.jpg
这就是我们在本部分中要做的所有内容。我希望你休息一下,回顾一下你刚刚学到的概念——并阅读提供的链接中的文档,以深入了解地形、Tilemap和TileSet。在下一部分中,我们将添加梯子以及其余的平台。现在是保存项目并备份项目的好时机,以便在出现任何破坏游戏的错误时可以恢复到这一部分。在继续本系列之前,回去复习你所学的内容,一旦你准备好了,我们下一部分见!

标签: Godot, 2D游戏, 游戏开发, 游戏自学, 2d, 横版游戏

添加新评论