设计GUI

现在,您已经掌握了基础知识,我们将了解如何构建具有可复用的UI组件的游戏图形用户界面(GUI):生命条、能量条、以及炸弹和翡翠计数器。在本教程结束时,您将拥有一个游戏GUI,可以使用GDscript或VisualScript进行控制:

../../_images/ui_gui_design_final_result.png

最终效果

您还将学习:

  1. 创建灵活的UI组件
  2. 使用场景继承
  3. 构建一个复杂的UI

下载项目文件: ui_gui_design.zip 并解压缩存档。在Godot中导入 start/ 项目以遵循本教程。end/ 文件夹包含最终结果。

注解

You can watch this tutorial as a video on YouTube.

分解UI

让我们分解最终的UI并计划将要使用的容器。和 设计标题画面 一样,它以 MarginContainer 开头。然后,我们最多可以看到三列:

  1. 左边是生命和能量的计数器
  2. 生命和能量条
  3. 右侧是炸弹和翡翠的计数器

但是条的标签和标尺是同一个UI元素的两个部分。如果我们这样想的话,就剩下两列了:

  1. 左边是生命和能量条
  2. 右侧是炸弹和翡翠的计数器

这使嵌套容器变得更加容易:我们使用 MarginContainer,在屏幕边框周围留有一些边距,然后使用 HBoxContainer 来管理两列。这两个条在 VBoxContainer 中叠加在一起。我们在右列中需要最后一个 HBoxContainer,来并排放置炸弹和翡翠计数器。

../../_images/ui_gui_step_tutorial_containers_structure.png

我们只用4个容器就能得到干净的UI布局

我们将需要在各个UI组件内添加额外的容器,但这为我们提供了主GUI场景的结构。有了这个计划,我们可以进入Godot并创建我们的GUI。

创建基本GUI

GUI有两种可能的方法:我们可以在单独的场景中设计元素并将它们放在一起,或者在一个场景中对所有内容进行原型制作,然后将其分解。建议使用单个场景,因为这样可以更快地处理UI的位置和比例。一旦看起来不错,就可以将节点树的整个部分保存为可重用子场景。我们稍后就做。

现在,让我们从几个容器开始。

创建一个新场景并添加一个 MarginContainer。选择节点并将其命名为 GUI

我们希望我们的界面锚定到屏幕的顶部。选择 GUI 节点并单击视图顶部的布局按钮。选择 Top Wide(顶部宽) 选项。默认情况下,GUI 节点将锚定到其父级的视图顶部。它将在垂直轴上自动调整大小,为其子UI组件腾出空间。

将场景另存为 GUI.tscn。我们将整个GUI放入其中。

选择 MarginContainer 后,前往属性检查器并向下滚动到自定义常量部分。展开它,然后点击每个 Margin 属性旁边的字段。将它们全部设置为 20 像素。接下来,添加一个 HBoxContainer 节点。接下来,添加一个`HBoxContainer`节点。这一个将在左侧包含我们的两个条,并将它们与右侧的两个计数器分开。

我们想将条形图垂直堆叠在 HBoxContainer 中。添加一个 VBoxContainer 作为 HBoxContainer 的子项,并将其命名为 Bars。再次选择父级 HBoxContainer,这次添加另一个 HBoxContainer 作为其子级。称之为 Counters。有了这四个容器,我们就为我们的GUI场景奠定了基础。

../../_images/ui_gui_containers_structure_in_godot.png

您应该有4个看起来像这样的容器

注解

之所以可以这样做,是因为我们首先分解了UI设计,并花了一些时间来考虑要使用的容器。当您学习这样的教程时,可能会觉得很奇怪。但是一旦您在真正的游戏中工作,您就会发现它是一种高效的工作流程。

创建条形基础

每个条形图拆分为两个水平对齐的子元素:带有健康计数的标签在左侧,量规在右侧。再次,HBoxContainer 是完成这项工作的理想工具。选择 Bars 节点,并在其中添加一个新的 HBoxContainer。将其命名为 Bar

标签本身至少需要三个节点:一个作为背景的 NinePatchRect,在其顶部,我们将在左侧添加一个纹理,即 HPEP,并在右侧用来显示值的一个 Label。我们可以根据需要嵌套 Control 节点。我们可以将 NinePatchRect 用作其他两个元素的父元素,因为它包含了它俩。通常,您想使用容器代替,因为它们的作用是帮助组织UI组件。无论如何,我们以后都需要一个 MarginContainer 来在生命计数和量规之间增加一些空间。选择 Bar 并添加一个 MarginContainer。将其命名为 Count。在其中添加三个节点:

  1. 一个名为 BackgroundNinePatchRect
  2. 一个名为 TitleTextureRect
  3. 和一个名为 NumberLabel

要将节点添加为兄弟节点,请始终先选择 Count 节点。

../../_images/ui_gui_step_tutorial_bar_template_1.png

您的场景树应如图所示。我们准备加入一些纹理

我们的场景仍然是空的。是时候加入一些纹理了。要加载纹理,请转到视图左侧的文件系统停靠面板。向下浏览到 res://assets/GUI 文件夹。

../../_images/ui_gui_step_tutorial_textures_in_FileSystem_tab.png

您应该会看到我们将用来为界面蒙皮的纹理列表。

在场景停靠面板中选择 Background。在属性检查器中,您应该看到一个 Texture 属性。在 FileSystem 选项卡中,点击并将 label_HP_bg.png 拖到 Texture 插槽中。它保持压扁。父 MarginContainer 将强制其大小降为0,直到我们强制容器中的元素具有最小大小。选择 Background 节点。在属性检查器面板中,向下滚动到 Rect 部分。设置 Min Size 为(100,40)。您应该会看到 Background 和它的父容器一起重调大小。

接下来,选择 Title 并将 label_HP.png 拖放到其 Texture 插槽中。选择 Number 节点,点击 Text 属性旁边的字段,然后键入 10。这样,我们可以在视图中看到两个节点。他们应该堆叠在其父级 MarginContainer 的左上角。

../../_images/ui_gui_step_tutorial_bar_label_stacked.png

如果您选择两个节点,您应该会看到这样的结果

As they have a container as their direct parent, we cannot move them freely: the Count node will always reset their anchors, their size and position. Try to move and resize the nodes in the viewport. Then, select any of the three textures and press Ctrl + Up or Ctrl + Down to reorder them in the Scene dock. They’ll snap back to their previous size and position.

父容器控制其直接子代的大小、比例、边距和锚点。若要修改节点,必须将它们嵌套在常规 Control 或另一个UI元素中。我们将使用 Background 作为 TitleNumber 的父元素。选择 TitleNumber,然后拖放到 Background 上。

../../_images/ui_gui_step_tutorial_bar_nesting_inside_background.png

通过将 Background 节点用作两个纹理的父级,我们将控制权从 Count、即 MarginContainer 节点移开

选择 Title,然后在属性检查器中,将其 拉伸模式(Stretch Mode) 属性更改为 保持居中(Keep Centered)。接下来,在属性检查器中找到 Rect 类别,并将 Size 属性更改为 (50, 40),使其仅占据背景的左半部分。接下来,选择 Number 节点。在视图中,点击 Layout 菜单并点击 Full Rect。节点将调整大小以适合 Background。前往属性检查器并将其 Align 属性更改为 Right,将 Valign 属性更改为 Center。文本应对齐到 Background 右边缘的中心。水平调整节点的大小,因此它占据了 Background 的右半部分,并且右边缘有一些填充。

../../_images/ui_gui_step_tutorial_bar_placed_title_and_label.png

这是节点的边界框在视图中的外观。保持粗糙,您现在不需要将它们放置得过于精确。

替换 Label 的字体

标签字体太小。我们需要更换它。选择 Number 节点,然后在属性检查器中,向下滚动到 Control 类,然后找到 Custom Font 类别。点击 Font 属性旁边的字段,然后点击 新建动态字体。再次点击该字段,然后选择编辑。

您将输入 动态字体(Dynamic Font) 资源。展开 字体(Font) 类别,然后点击 字体数据(Font Data) 旁边的字段。点击 加载(Load) 按钮。在文件浏览器中,向下浏览到 assets/font 文件夹,然后双击 Comfortaa-Bold.ttf 将其打开。您应该在视图中看到字体更新。展开设置类别以更改字体大小。将 Size 属性设置为较高的值,例如 2428

现在,我们需要文本的基线,即数字的下边缘,以与左侧的 HP 纹理对齐。为此,仍在 DynamicFont 资源中,您可以在 Extra Spacing 类别下调整 Bottom 属性。它为文本添加了一些底部填充。点击场景选项卡中的 Number 节点,返回到节点属性,然后将 Valign 更改为 Bottom。要调整文本的基线,请再次单击 自定义字体(Custom Font) 类别下的 字体 字段,并调整 底部(Bottom) 属性,直到文本与 标题(Title) 节点对齐。这里使用了 2 像素的值。

../../_images/ui_gui_step_tutorial_number_baseline.png

底部值为2像素,数字与标题对齐

至此,我们完成了GUI中最难的部分。恭喜您!让我们继续讨论更简单的节点。

添加进度条

我们需要最后一个元素来完成我们的生命条:量规本身。Godot附带了一个 TextureProgress 节点,该节点具有我们所需的一切。

选择 Bar 节点,并在其中添加 TextureProgress。命名为 Gauge。在属性检查器中展开 纹理(Textures) 部分。转到 FileSystem 停靠面板,然后将 lifebar_bg.png 纹理拖放到 Under 插槽中。对 lifebar_fill.png 图像执行相同的操作,然后将其拖放到 Progress 插槽中。在属性检查器面板的 Range 类下,将 Value 属性更改为 50,以查看量规是否被填满。

只有五个 Control 节点,我们的第一个条可以使用了。

../../_images/ui_gui_step_tutorial_bar_final.png

就是这样,我们的生命条已准备就绪。最后一部分很快,不是吗?这要归功于我们强大的容器设置。

设计炸弹和翡翠计数器

炸弹和翡翠计数器就像条形图的 Count 节点一样。因此,我们将其复制并用作模板。

Under the Bar node, select Count and press Ctrl + D to duplicate it. Drag and drop the new node under the Counters HBoxContainer at the bottom of the scene tree. You should see it resize automatically. Don’t worry about this for now, we’ll fix the size soon.

Count2 节点重命名为 Counter 。与条形图不同,我们希望数字在左边,图标在右边。设置是相同的:我们需要一个背景( NinePatchRect )、标题、和数字节点。Title 节点是一个 TextureRect,这就是我们需要用来显示图标。在场景树中,选择 Title 节点,并将其重命名为 Icon

../../_images/ui_gui_step_tutorial_counter_design_1.png

到目前为止,这是您的节点树的外观

选中 Icon 节点后,在属性检查器中,滚动到顶部以查看 Texture 插槽。转到左侧的 FileSystem 停靠面板,然后选择 bombs_icon.png。将其拖放到 Texture 插槽中。在场景选项卡中,选择 IconNumber 节点。点击视图顶部工具栏中的 Layout 菜单,然后选择 全角(Full Rect)。两个节点都将更新以适合 背景(Background) 的大小。

../../_images/ui_gui_step_tutorial_counter_design_2.png

节点锚定到整个背景,但是它们的位置不正确

Let’s change the Number‘s align properties to move it to the left and center of the Background. Select the Number node, change its Align property to left and the Valign property to center. Then resize its left edge a bit to add some padding between the left edge of the Background and the text.

../../_images/ui_gui_step_tutorial_counter_design_3.png

The Number node aligned to the left and center

要使 Icon 和背景叠加,我们需要进行一些调整。首先,我们的背景有点高。这是因为它位于由最顶层GUI节点控制的边距容器内。选择场景树顶部的GUI节点,并垂直缩小其尺寸,以使其尽可能薄。您会看到仪表防止您把它做得太小。容器不能小于其子容器的最小大小。容器的边距也很重要。

选择图标,点击布局菜单,然后选择 全矩形(Full Rect) 以重新居中。我们需要它锚定到 Background 的右边缘。再次打开 Layout 菜单并选择 右居中(Center Right)。向上移动图标,使其垂直居中于 Background

../../_images/ui_gui_step_tutorial_counter_design_4.png

炸弹图标锚定在 背景(Background) 的右边缘。调整 计数器(Counter) 容器的大小,以查看粘在其右侧的 图标(Icon) 节点

因为我们从条形图的 Count 中复制了 Counter,所以 Number 节点的字体关闭了。再次选择 Number 节点,转到 Font 属性,然后点击它访问 DynamicFont 资源。在 Extra Spacing 部分,将 Bottom 值改为 0 以重置字体的基线。我们的计数器现在运作正常。

让我们将 Counters 锚定到视图的右边缘。为此,我们需要将 Bars 容器设置为占用所有可用的水平空间。选择 Bars 节点,然后向下滚动到 Size Flags 类别。在 Horizontal 类别中,检查 Expand 值。Bars 节点应调整大小并将计数器推到屏幕右侧。

../../_images/ui_gui_step_tutorial_counter_design_5.png

一个扩展的容器会占用其父容器的所有空间,推动沿途的其他一切东西

将条形图和计数器变成可复用的UI组件

我们有一个条形图和一个计数器小部件。但是我们每种都需要两个。稍后我们可能需要更改条形图的设计或功能。如果我们可以有一个场景来存储UI元素的模板,并让子场景来处理变体,那就太好了。Godot让我们通过继承的场景做到这一点。

让我们将 CounterBar 分支保存为单独的场景,将它们简化为创建 LifeBarEnergyBarBombCounter、和 EmeraldCounter。选择 Bar HBoxContainer。右键点击它,然后点击 将分支另存为场景(Save Branch as Scene)。将场景保存为 Bar.tscn。您应该看到节点分支将其变为单个 Bar 节点。

小技巧

场景是节点树。最顶部的节点是树的 ,层次结构底部的子级是 。除根节点以外的任何节点以及一个或多个子节点都是 分支。我们可以将节点分支封装到单独的场景中,或者将它们从其他场景加载并合并到活动场景中。右键点击场景停靠面板中的任何节点,然后选择 将分支另存为场景(Save Branch as Scene)从场景合并(Merge from Scene)

然后,选择 Counter 节点并执行相同的操作。右键单击 将分支另存为场景(Save Branch as Scene),并将其保存为 Counter.tscn。新的编辑场景图标将显示在场景树中节点的右侧。点击 Bar 旁边的一个以打开相应的场景。调整 Bar 节点的大小,使其边界框适合其内容。我们以命名和放置 Control 节点的方式,已经准备好继承此模板并创建生命条。同理操作 Counter

../../_images/ui_gui_step_tutorial_bar_template_scene.png

无需额外更改,我们的条形图即可使用

使用场景继承创建其余元素

我们需要两个条的工作方式相同:它们的左侧应有标签,并带有一些值,右侧应有水平量规。唯一的区别是一个带有 HP 标签,并且是绿色的,而另一个叫做 EP,并且是黄色的。Godot为我们提供了一个强大的工具,可以为游戏中的所有条形图创建通用基础:继承的场景

../../_images/gui_step_tutorial_gui_scene_hierarchy.png

继承的场景可帮助我们保持GUI场景的整洁。最后,每个UI组件只有一个容器和一个节点。

在继承的场景上,除了名称之外,还可以更改属性检查器中每个节点的任何属性。如果修改并保存父场景,则所有继承的场景都会更新以反映所做的更改。如果您在继承的场景中更改值,它将始终覆盖父级的属性。这对于UI很有用,因为它们经常需要相同元素的变体。通常,在UI设计中,按钮、面板等具有相同的基本样式和交互。我们不想手动将其复制到所有变体中。

重新加载图标将出现在您重写的属性旁边。点击它可将值重置为父场景的默认值。

注解

将场景继承想像成节点树,或者在GDScript中使用 extends 关键字。继承的场景会像其父级一样执行所有操作,但是您可以覆盖属性、资源并添加额外的节点和脚本来扩展其功能。

继承 Bar 场景来构建生命条

Go to Scene -> New Inherited Scene to create a new type of Bar. Select the Bar scene and open it. You should see a new [unsaved] tab, that’s like your Bar, but with all nodes except the root in grey. Press Ctrl + S (Cmd + S on macOS) to save the new inherited scene and name it LifeBar.

../../_images/ui_gui_step_tutorial_inherited_scene_parent.png

您不能重命名灰色节点。这表示它们有一个父级场景

首先,将根节点或顶级节点重命名为 LifeBar。我们始终希望根目录准确描述此UI组件是什么。该名称与接下来要创建的 EnergyBar 区别开来。场景内的其他节点应使用广义术语来描述组件的结构,因此它适用于所有继承的场景。就像我们的 TextureProgressNumber 节点一样。

注解

如果您曾经做过网页设计,则它与使用CSS的精神相同:创建基类,并使用修饰符类添加变体。在基本的按钮类中,您将具有绿色的按钮和红色的按钮供用户接受和拒绝提示。新类包含父元素的名称和一个额外的关键字,以说明其修改方式。创建继承的场景并更改顶级节点的名称时,我们正在做相同的事情。

设计 EnergyBar

我们已经使用主 Bar 场景设置了 LifeBar 的设计。现在我们需要 EnergyBar

让我们创建一个新的继承场景,然后再次选择 Bar.tscn 场景并将其打开。双击 Bar 根节点,并将其重命名为 EnergyBar。保存新场景为 EnergyBar.tscn。我们需要用 EP 的纹理替换 HP 纹理,并更改量规上的纹理。

转到左侧的 FileSystem 停靠面板,在场景树中选择 Title 节点,然后将 label_EP.png 文件拖放到纹理插槽中。选择 Number 节点,然后将 Text 属性更改为另一个值,例如 14

您会注意到 EP 纹理比 HP 纹理小。我们应该更新 Number 的字体大小以更好地适应它。字体是一种资源。使用此资源的整个项目中的所有节点都将受到我们更改的任何属性的影响。您可以尝试将大小更改为较大的值,例如 40,然后切换回 LifeBarBar 场景。您将看到文本的大小增加了。

../../_images/ui_gui_step_tutorial_design_EnergyBar_1.png

如果我们更改字体资源,则使用该字体资源的所有节点都会受到影响

要仅在此节点上更改字体大小,我们必须创建字体资源的副本。再次选择 Number 节点,并点击属性检查器面板右上角的扳手和螺丝刀图标。在下拉菜单中,选择 使子资源唯一(Make Sub-Resources Unique) 选项。Godot将找到该节点使用的所有资源,并为我们创建唯一的副本。

../../_images/ui_gui_step_tutorial_design_EnergyBar_2.png

使用此选项为一个节点创建资源的唯一副本

小技巧

When you duplicate a node from the Scene tree, with Ctrl + D (Cmd + D on macOS), it shares its resources with the original node. You need to use Make Sub-Resources Unique before you can tweak the resources without affecting the source node.

向下滚动到 自定义字体(Custom Font) 部分,然后打开 字体(Font)。将 大小(Size) 降低到较小的值,例如 2022。您可能还需要调整 底部(Bottom) 间距值,以将文本的基线与左侧的 EP 标签对齐。

../../_images/ui_gui_step_tutorial_design_EnergyBar_3.png

EP Count 小部件,其字体比 HP 同类小

现在,选择 TextureProgress 节点。拖动 energy_bar_bg.png 文件到 Under 槽,并对 energy_bar_fill.png 执行相同的操作。然后把它放到 Progress 纹理槽中。

您可以垂直调整节点的大小,以使其边界矩形适合量规。对 Count 节点执行相同的操作,直到其大小与条的大小对齐为止。因为 TextureProgress 的最小大小是根据其纹理设置的,所以您将无法缩小 Count 节点低于该值大小。这也是 Bar 容器将具有的大小。您也可以缩小此尺寸。

最后但并非最不重要的一点是,背景(Background) 容器的最小尺寸使其变得有点大。选择它,然后在 矩形(Rect) 部分中,将 最小大小(Min Size) 属性更改为 80 像素。它应该自动调整大小,并且 TitleNumber 节点也应重新定位。

../../_images/ui_gui_step_tutorial_design_EnergyBar_4.png

Count 看起来好多了,现在小了一点

小技巧

Count 节点的大小会影响 TextureProgress` 的位置。由于我们稍后将使条形图垂直对齐,因此最好使用 Counter 的左边距来调整 EP 标签的大小。这样,EnergyBarCountLifeBarCount 节点都为 100 像素宽,因此两个量规将完美对齐。

准备炸弹和翡翠计数器

现在让我们来处理计数器。进入 场景 -> 新继承的场景(Scene -> New Inherited Scene),选择 Counter.tscn 作为基础。也将根节点重命名为 BombCounter。将新场景保存为 BombCounter.tscn。这就是这个场景的全部内容。

../../_images/ui_gui_step_tutorial_design_counters_1.png

炸弹计数器与原始计数器场景相同

Go to Scene -> New Inherited Scene again and select Counter.tscn once more. Rename the root node EmeraldCounter and save the scene as EmeraldCounter.tscn. For this one, we mainly need to replace the bomb icon with the emerald icon. In the FileSystem tab, drag the emeralds_icon.png onto the Icon node’s Texture slot. Icon already anchors to the right edge of the Background node so we can change its position and it will scale and reposition with the EmeraldCounter container. Shift the emerald icon a bit to the right and down. Use the Arrow Keys on the keyboard to nudge its position. Save, and we’re done with all the UI elements.

../../_images/ui_gui_step_tutorial_design_counters_2.png

翡翠计数器看起来应该像这样

将UI组件添加到最终的GUI

是时候将所有UI元素添加到主GUI场景中了。再次打开 GUI.tscn 场景,并删除 BarCounter 节点。在 FileSystem 停靠面板中,找到 LifeBar.tscn 并将其拖放到场景树中的 Bars 容器上。对 EnergyBar 执行相同的操作。您应该看到它们垂直对齐。

../../_images/ui_gui_step_tutorial_assemble_final_gui_1.png

LifeBarEnergyBar 自动对齐

现在,将 BombCounter.tscnEmeraldCounter.tscn 场景拖放到 Counters 节点上。它们会自动调整大小。

../../_images/ui_gui_step_tutorial_assemble_final_gui_2.png

调整节点大小以占用所有可用的垂直空间

为了让 EmeraldCounterBombCounter 使用我们在 Counter.tscn 中定义的大小,我们需要在 Counters 容器上更改 Size Flags。选择 Counters 节点,然后在属性检查器中展开 Size Flags 部分。取消选中 垂直(Vertical) 属性的 填充(Fill) 标记,然后选中 收缩中心(Shrink Center),以便容器在 HBoxContainer 内部居中。

../../_images/ui_gui_step_tutorial_assemble_final_gui_3.png

现在,这两个计数器都有一个像样的大小

小技巧

更改 Counters 容器的 Min Size 属性,以控制计数器背景的高度。

我们在 EnergyBar 上留下一个带有 EP 标签的小问题:2个条应垂直对齐。点击 EnergyBar 节点旁边的图标以打开其场景。选择 Count 节点,然后向下滚动到 Custom Constants 部分。添加一个 20Margin Left。在 Rect 部分,将节点的 Min Size 设置为100,与 LifeBar 相同。Count 现在应该在左边有一些边距。如果您保存并返回GUI场景,它将与 LifeBar 垂直对齐。

../../_images/ui_gui_step_tutorial_assemble_final_gui_4.png

2个条形图完美对齐

注解

我们原本可以这样设置 EnergyBar。但是,这表明您可以随时返回任何场景进行调整,并查看更改在项目中的传播!

把GUI放到游戏的模型上

为了结束本教程,我们将GUI插入到游戏的模型场景中。

前往文件系统停靠面板,打开 LevelMockup.tscn

GUI.tscn 场景拖放到 bg 节点下方和 Character 上方。GUI将缩放以适合整个视图。前往 Layout 菜单并选择 Center Top 选项,使其锚定到游戏窗口的顶部边缘。然后调整GUI的大小,使其在垂直方向上尽可能小。现在,您可以看到界面在游戏中的外观。

祝贺您完成了这篇冗长的教程。最终项目您可以在 ui_gui_design.zip 找到。

../../_images/ui_gui_design_final_result.png

最终效果

注解

A final note about Responsive Design. If you resize the GUI, you’ll see the nodes move, but the textures and text won’t scale. The GUI also has a minimum size, based on the textures inside of it. In games, we don’t need the interface to be as flexible as that of a website. You almost never want to support both landscape and portrait screen orientations. It’s one or the other. In landscape orientation, the most common ratios range from 4:3 to 16:9. They are close to one another. That’s why it’s enough for the GUI elements to only move horizontally when we change the window size.