介绍

Godot 的场景系统虽然强大且灵活,但有一个缺点:没有方法可以存储多个场景所需的信息(例如玩家的分数或库存)。

可以通过一些变通方法解决此问题,但它们有其自身的局限性:

  • 您可以使用加载和卸载其他场景作为其子场景的“主”场景。但是,这意味着您不能再单独运行这些场景并期望它们正常工作。
  • 信息可以存储到磁盘中user://,然后由需要它的场景加载,但频繁保存和加载数据很麻烦,而且速度可能很慢。

Singleton模式是解决,你需要存储场景之间持久信息的常见情况的有用工具。在我们的例子中,只要名称不同,就可以为多个单例重用相同的场景或类。

使用此概念,您可以创建具有以下特性的对象:

  • 始终加载,无论当前正在运行哪个场景。
  • 可以存储玩家信息等全局变量。
  • 可以处理切换场景和场景间转换。
  • 像一个单例一样工作,因为GDScript不设计支持全局变量。

自动加载节点和脚本可以为我们提供这些特性。

根据单例设计模式,Godot 不会使 AutoLoad 成为“真正的”单例。如果需要,用户可能仍会多次实例化它。

自动加载

您可以创建自动加载以加载从Node继承的场景或脚本 。

自动加载脚本时,将创建一个节点并将脚本附加到它。在加载任何其他场景之前,此节点将被添加到根视口。

../../_images/singleton.png

自动加载一个场景或脚本,选择项目>项目设置从菜单中切换到自动加载标签。

../../_images/autoload_tab.png

您可以在此处添加任意数量的场景或脚本。列表中的每个条目都需要一个名称,该名称被指定为节点的name属性。条目添加到全局场景树时的顺序可以使用向上/向下箭头键进行操作。

../../_images/autoload_example.png

这意味着任何节点都可以通过以下方式访问名为“PlayerVariables”的单例:

var player_vars = get_node("/root/PlayerVariables")
player_vars.health -= 10

如果选中Enable列(这是默认值),则可以直接访问单例,而无需get_node()

PlayerVariables.health -= 10

请注意,访问自动加载对象(脚本和/或场景)就像场景树中的任何其他节点一样。事实上,如果您查看正在运行的场景树,您会看到自动加载的节点出现:

../../_images/autoload_runtime.png

自定义场景切换器

本教程将演示如何使用自动加载功能构建场景切换器。对于基本的场景切换,您可以使用 SceneTree.change_scene() 方法(有关详细信息,请参阅SceneTree)。但是,如果在更改场景时需要更复杂的行为,则此方法可提供更多功能。

首先,从这里下载模板: autoload.zip并在 Godot 中打开它。

该项目包含两个场景:Scene1.tscnScene2.tscn。每个场景都包含一个显示场景名称的标签和一个pressed()连接其信号的按钮 。运行项目时,项目从中开始 Scene1.tscn。但是,按下按钮没有任何作用。

Global.gd

切换到脚本选项卡并创建一个名为 的新脚本Global.gd。确保它继承自Node

../../_images/autoload_script.png

下一步是将此脚本添加到自动加载列表中。从菜单中打开 Project > Project Settings,切换到AutoLoad选项卡并通过单击浏览按钮或键入其路径来选择脚本: res://Global.gd. 按添加将其添加到自动加载列表中:

../../_images/autoload_tutorial1.png

现在,每当我们在项目中运行任何场景时,都会始终加载此脚本。

回到脚本,它需要在_ready()函数中获取当前场景 。当前场景(带有按钮的场景)和 Global.gd根节点的子节点,但自动加载的节点始终是第一个。这意味着 root 的最后一个孩子总是加载的场景。

现在我们需要一个改变场景的函数。此功能需要释放当前场景并替换为请求的场景。
func goto_scene(path):
    # This function will usually be called from a signal callback,
    # or some other function in the current scene.
    # Deleting the current scene at this point is
    # a bad idea, because it may still be executing code.
    # This will result in a crash or unexpected behavior.

    # The solution is to defer the load to a later time, when
    # we can be sure that no code from the current scene is running:

    call_deferred("_deferred_goto_scene", path)


func _deferred_goto_scene(path):
    # It is now safe to remove the current scene
    current_scene.free()

    # Load the new scene.
    var s = ResourceLoader.load(path)

    # Instance the new scene.
    current_scene = s.instance()

    # Add it to the active scene, as child of root.
    get_tree().get_root().add_child(current_scene)

    # Optionally, to make it compatible with the SceneTree.change_scene() API.
    get_tree().set_current_scene(current_scene)

使用Object.call_deferred(),第二个函数只会在当前场景中的所有代码都完成后运行。因此,当前场景仍在使用中(即其代码仍在运行)不会被删除。

最后,我们需要填充两个场景中的空回调函数:

func _on_Button_pressed():
    Global.goto_scene("res://Scene2.tscn")
func _on_Button_pressed():
    Global.goto_scene("res://Scene1.tscn")
运行项目并测试您可以通过按下按钮在场景之间切换。

当场景较小时,过渡是瞬时的。但是,如果您的场景更复杂,它们可能需要很长时间才能出现。要了解如何处理此问题,请参阅下一个教程:后台加载

或者,如果加载时间相对较短(少于 3 秒左右),您可以通过在更改场景之前显示某种 2D 元素来显示“加载牌匾”。然后,您可以在场景更改后立即隐藏它。这可用于向玩家指示正在加载场景。