在本教程中,您将角色连接到生命栏并为健康损失设置动画。

../../_images/lifebar_tutorial_final_result.gif

您将学习:

  • 如何通过信号角色连接到GUI
  • 如何使用GDscript控制GUI
  • 如何使用Tween节点设置生命线动画

在编写游戏代码时,您要首先构建核心游戏玩法:主要机制,玩家输入,获胜和失败条件。UI稍后出现。如果可能的话,您希望将构成项目的所有元素分开。每个角色都应位于自己的场景中,并带有自己的脚本,UI元素也应位于自己的场景中。这样可以防止错误,使您的项目易于管理,并允许不同的团队成员在游戏的不同部分上工作。

一旦核心游戏和UI准备就绪,您就需要以某种方式将它们连接起来。在我们的示例中,我们的敌人以固定的时间间隔攻击玩家。我们希望生命值在玩家受到伤害时更新。

为此,我们将使用信号

信号是Godot的Observer模式的版本。它们使我们能够发送一些消息。其他节点可以连接到发出信号并接收信息的对象。这是一个功能强大的工具,我们经常在用户界面和成就系统中使用它。不过,您不想在任何地方使用它们。连接两个节点之间会增加一些耦合。当连接很多时,它们变得难以管理。有关更多信息,请查看GDquest上的信号视频教程

下载并探索启动项目

下载Godot项目:ui_code_life_bar.zip。它包含您开始需要的所有资产和脚本。解压缩.zip存档以获取两个文件夹:startend

start项目加载到Godot中。在FileSystem扩展坞中,双击“ LevelMockup.tscn”将其打开。这是RPG游戏的模型,其中两个角色彼此面对。粉红色的敌人会定期攻击并破坏绿色方块,直到其死亡。随意尝试游戏:基本的战斗机制已经起作用。但是由于角色没有连接到生命条,所以GUI它什么也没做。

这是编写游戏代码的典型方式:首先实现核心游戏玩法,处理玩家的死亡,然后才添加界面。这是因为UI会监听游戏中发生的事情。因此,如果还没有其他系统,它将无法正常工作。如果您在原型设计和测试游戏玩法之前设计UI,则可能会无法正常工作,并且您必须从头开始重新创建它。

该场景包含一个背景精灵,一个GUI和两个角色。

GUI场景封装了游戏的所有图形用户界面。它带有一个准系统脚本,在该脚本中,我们获得了场景中存在的节点的路径:

onready var number_label = $Bars/LifeBar/Count/Background/Number
onready var bar = $Bars/LifeBar/TextureProgress
onready var tween = $Tween
  • number_label将生命计数显示为数字。这是一个Label 节点
  • bar是进度条本身。这是一个TextureProgress节点
  • tween 是一个组件样式的节点,可以对来自任何其他节点的任何值或方法进行动画处理和控制

使用玩家的max_health设置生命线

我们必须以某种方式告诉GUI玩家当前的健康状况,以更新生命线的纹理,并在屏幕左上角的HP计数器中显示剩余的健康状况。为此,我们会在每次受到伤害时将玩家的生命值发送到GUI。然后,GUI将使用此值更新LifebarNumber节点。

我们可以在此处停止显示数字,但是我们需要初始化条形图max_value以使其按正确的比例进行更新。因此,第一步是告诉GUI绿色字符max_health是什么 。

默认情况下,该条TextureProgressmax_value100。如果不需要用数字显示角色的健康状况,则无需更改其max_value属性。您可以从播放器GUI发送一个百分比: health / max_health * 100

单击“GUI场景”底座中右侧的脚本图标以打开其脚本。在_ready功能,我们去保存Player的一个新的变量max_health,并用它来设置 barmax_value

func _ready():
    var player_max_health = $"../Characters/Player".max_health
    bar.max_value = player_max_health

让我们分解一下。$"../Characters/Player"是在场景树中向上一个节点并从Characters/Player那里检索节点的一种简写形式 。它使我们能够访问该节点。语句的第二部分.max_health访问 max_healthPlayer节点上的。

第二行将此值分配给bar.max_value。您可以将这两行合并为一,但是player_max_health在本教程的后面,我们将需要再次使用 。

Player.gd在游戏开始设置health=max_health,所以我们可以用这方面的工作。为什么我们仍然使用max_health?有两个原因:

我们不能保证health永远相等 max_health:游戏的未来版本可能会加载玩家已经失去一些生命值的等级。

当您在游戏中打开场景时,Godot会按照场景底座中的顺序从上到下逐一创建节点。GUI播放器不属于同一节点分支。为了确保当彼此访问时它们都存在,我们必须使用_ready函数。在游戏开始之前,Godot在加载所有节点后立即调用_ready。这是设置所有内容并准备游戏会话的完美功能。了解有关_ready:脚本的更多信息(续)

当玩家受到打击时,通过信号更新生命值

我们的GUI已准备好从接收health值更新 Player。为了实现这一点,我们将使用信号

有很多有用的内置信号,例如enter_treeexit_tree,所有节点在分别创建和销毁时都会发出。您也可以使用signal关键字创建自己的关键字。在播放器节点,你会发现我们为您创建了两个信号:died和health_changed

为什么我们不直接Player_process 函数中获取节点并查看运行状况值?通过这种方式访问​​节点会在它们之间建立紧密的耦合。如果您谨慎执行,则可能会起作用。随着游戏规模的扩大,您可能会有更多的联系。如果以这种方式获取节点,它将很快变得复杂。不仅如此:您还需要听_process 功能中不断变化的状态。该检查每秒发生60次,由于代码的运行顺序,您可能会中断游戏。

在给定的帧上,您可能会在更新之前查看另一个节点的属性:您从上一帧获得了一个值。这会导致难以修复的模糊错误。另一方面,发生更改后立即发出信号。它可以确保您获得新的信息。更改发生,您将立即更新连接节点的状态 。

信号所源自的观察者模式仍然在节点分支之间增加了一些耦合。但是,与直接访问节点以在两个单独的类之间进行通信相比,它通常更轻便,更安全。父节点可以从其子节点获取值是可以的。但是,如果您正在使用两个单独的分支,则希望使用信号。阅读游戏编程模式以获取有关观察者模式的更多信息。该全书是可以在网上免费。

考虑到这一点,让我们将其连接GUIPlayer。单击Player场景底座中的节点以将其选中。向下转到检查器,然后单击“节点”选项卡。这是连接节点以收听您选择的节点的地方。

第一部分列出了在中定义的自定义信号Player.gd

  • died角色死亡时发射。稍后我们将使用它来隐藏UI。
  • health_changed 当角色被击中时发射。

选择health_changed并单击右下角的“连接”按钮以打开“连接信号”窗口。在左侧,您可以选择将收听此信号的节点。选择GUI节点。在屏幕的右侧,您可以将可选值与信号打包在一起。我们已经在中进行了处理Player.gd。通常,我建议不要在此窗口中添加太多参数,因为它们不如从代码中方便地使用。

../../_images/lifebar_tutorial_connect_signal_window_health_changed.png

您可以选择通过代码连接节点。但是,从编辑器执行此操作有两个优点:

  1. Godot可以在连接的脚本中为您编写新的回调函数
  2. 发射器图标出现在场景底座中发射信号的节点旁边。

在窗口底部,您将找到所选节点的路径。我们对第二行“节点中的方法”感兴趣。这GUI是发出信号时在节点上调用的方法。该方法接收与信号一起发送的值,并让您对其进行处理。如果您向右看,默认情况下会打开一个“ Make Function”单选按钮。单击窗口底部的连接按钮。Godot在GUI 节点内部创建方法。脚本编辑器随即打开,其中光标位于新 _on_Player_health_changed函数内。

笔记

当您从编辑器连接节点时,Godot会使用以下模式生成方法名称:_on_EmitterName_signal_name。如果您已经编写了该方法,则“ Make Function”选项将保留该方法。您可以将名称替换为任何您想要的名称。

../../_images/lifebar_tutorial_godot_generates_signal_callback.png

在函数名称后的括号内,添加一个player_health 参数。当播放器发出health_changed信号时,它将在其health旁边发送其电流。您的代码应如下所示:

func _on_Player_health_changed(player_health):
    pass

该引擎不会将PascalCase转换为snake_case,对于C#示例,我们将使用PascalCase作为方法名称,使用camelCase作为方法参数,这遵循官方的C#命名约定。

在内部_on_Player_health_changed,让我们调用第二个函数 update_health,并将其传递给player_health变量。

我们可以直接在LifeBarNumber上更新运行状况值。改为使用此方法有两个原因:

  1. 这个名称可以让我们未来的自己和队友清楚地知道,当玩家受到伤害时,我们会在GUI上更新生命值
  2. 我们稍后会重用此方法

_on_Player_health_changed在下面创建一个新方法update_health。它以new_value作为唯一参数:

func update_health(new_value):
    pass

此方法需要:

  • 设置Number节点是textnew_value转换为字符串
  • 设定TextureProgressvaluenew_value
func update_health(new_value):
    number_label.text = str(new_value)
    bar.value = new_value

str是一个内置函数,可将任何值转换为文本。Numbertext属性需要一个字符串,因此我们无法将其new_value直接分配给

还要update_health_ready函数结尾处调用,以在游戏开始时用正确的值初始化Number节点text。按此F5测试游戏:每次攻击时,生命线都会更新!

../../_images/lifebar_tutorial_LifeBar_health_update_no_anim.gif

当玩家受到打击时,Number节点和TextureProgress都会更新

使用“ Tween”节点对生命损失进行动画处理

我们的界面可以使用,但是可以使用一些动画。这是引入Tween节点的好机会,该节点是使属性产生动画的基本工具。Tween在特定的时间范围内,从开始到结束,为您想要的任何内容设置动画。例如,当角色受到伤害时,它可以TextureProgress从当前级别到到Player新级别 的生命值进行动画health处理。

GUI场景已经包含一个Tween存储在tween变量中的子节点 。现在使用它。我们必须对进行一些更改 update_health

我们将使用Tween节点的interpolate_property方法。它包含七个参数:

  1. 对拥有要进行动画处理的属性的节点的引用
  2. 属性的标识符作为字符串
  3. 起始值
  4. 终值
  5. 动画的持续时间(以秒为单位)
  6. 过渡类型
  7. 与方程式结合使用的缓动性。

最后两个参数组合起来对应一个缓动方程。这可以控制值从起点到终点的演变方式。

单击GUI节点旁边的脚本图标以再次将其打开。所述 Number节点需要文本进行自我更新,并且Bar需要一个浮动或整数。我们可以用来interpolate_property给数字设置动画,但不能直接给文本设置动画。我们将使用它为GUI名为的新变量设置动画animated_health