싱글톤(오토로드)(Singletons(AutoLoad))

소개

Godot의 씬 시스템은, 강력하고 유연하지만, 단점이 있습니다: 하나 이상의 씬이 필요하기 때문에 정보를 저장할 방법이 없습니다 (예: 플래이어의 점수나 인벤토리).

이러한 문제들을 해결할 방법은 있지만, 그것만의 한계점이 있습니다:

  • 다른 씬을 자식으로 불러오거나 그렇지 않을 "마스터" 씬을 사용할 수 있습니다. 하지만 그렇게 하면 바르게 작동하기 위해 더 이상 씬들을 개별적으로 실행할 수 없다는 것을 의미합니다.
  • 정보를 user:// 에 저장하고 필요할 때 불러올 수 있습니다, 하지만 자주 데이터를 저장하고 불러오는 것은 성가시고 느려질 수 있습니다.

The Singleton pattern is a useful tool for solving the common use case where you need to store persistent information between scenes. In our case, it's possible to reuse the same scene or class for multiple singletons as long as they have different names.

이 개념을 사용해서, 다음과 같은 객체를 만드실 수 있습니다:

  • Are always loaded, no matter which scene is currently running.
  • Can store global variables such as player information.
  • Can handle switching scenes and between-scene transitions.
  • Act like a singleton, since GDScript does not support global variables by design.

노드와 스크립트를 Autoload하는 것은 위와 같은 기능을 제공합니다.

주석

Godot won't make an AutoLoad a "true" singleton as per the singleton design pattern. It may still be instanced more than once by the user if desired.

오토로드

You can create an AutoLoad to load a scene or a script that inherits from Node.

주석

When autoloading a script, a Node will be created and the script will be attached to it. This node will be added to the root viewport before any other scenes are loaded.

../../_images/singleton.png

To autoload a scene or script, select Project > Project Settings from the menu and switch to the AutoLoad tab.

../../_images/autoload_tab.png

여기서 원하는 씬이나 스크립트를 추가하실 수 있습니다. 목록에서 각 항목에는 노드의 name 속성으로 지정하는 이름이 필요합니다. 전역 씬 트리에 추가된 항목의 순서는 위/아래 방향키를 사용해 조작할 수 있습니다.

../../_images/autoload_example.png

위의 경우, 어느 노드든지 "PlayerVariables" 라는 싱글톤을 접근할 수 있습니다:

var player_vars = get_node("/root/PlayerVariables")
player_vars.health -= 10
var playerVariables = (PlayerVariables)GetNode("/root/PlayerVariables");
playerVariables.Health -= 10; // Instance field.

If the Enable column is checked (which is the default), then the singleton can be accessed directly without requiring get_node():

PlayerVariables.health -= 10
// Static members can be accessed by using the class name.
PlayerVariables.Health -= 10;

씬 트리에서 다른 노드와 마찬가지로 오토로드 객체 (스크립트 혹은 씬)는 접근됩니다. 사실, 실행하는 씬 트리를 보신다면, 오토로드 된 노드가 나타나는 것을 보실 수 있습니다:

../../_images/autoload_runtime.png

맞춤 씬 전환기

This tutorial will demonstrate building a scene switcher using autoloads. For basic scene switching, you can use the SceneTree.change_scene() method (see SceneTree(씬 트리) for details). However, if you need more complex behavior when changing scenes, this method provides more functionality.

시작하기 위해, 여기서 템플릿을 다운로드 하시고 autoload.zip 그리고 Godot에서 여세요.

프로젝트는 두 개의 씬을 갖고 있습니다: Scene1.tscnScene2.tscn 입니다. 각 씬은 씬의 이름을 보여주는 라벨과 pressed() 시그널이 연결된 버튼이 있습니다. 프로젝트를 실행할 때, Scene1.tscn 에서 시작합니다. 하지만, 버튼을 눌러도 아무렇지 않습니다.

Global.gd

Switch to the Script tab and create a new script called Global.gd. Make sure it inherits from Node:

../../_images/autoload_script.png

The next step is to add this script to the autoLoad list. Open Project > Project Settings from the menu, switch to the AutoLoad tab and select the script by clicking the browse button or typing its path: res://Global.gd. Press Add to add it to the autoload list:

../../_images/autoload_tutorial1.png

이제 언제든지 우리는 어떤 씬이 프로젝트에서 실행하든지, 이 스크립트가 항상 불러와질 것입니다.

Returning to the script, it needs to fetch the current scene in the _ready() function. Both the current scene (the one with the button) and Global.gd are children of root, but autoloaded nodes are always first. This means that the last child of root is always the loaded scene.

extends Node

var current_scene = null

func _ready():
    var root = get_tree().get_root()
    current_scene = root.get_child(root.get_child_count() - 1)
using Godot;
using System;

public class Global : Godot.Node
{
    public Node CurrentScene { get; set; }

    public override void _Ready()
    {
        Viewport root = GetTree().GetRoot();
        CurrentScene = root.GetChild(root.GetChildCount() - 1);
    }
}

이제, 우리는 씬을 바꾸기 위한 함수가 필요합니다. 이 함수는 현재 씬을 해제하고 요청하는 것으로 바꾸어야 합니다.

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)
public void GotoScene(string path)
{
    // This function will usually be called from a signal callback,
    // or some other function from 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:

    CallDeferred(nameof(DeferredGotoScene), path);
}

public void DeferredGotoScene(string path)
{
    // It is now safe to remove the current scene
    CurrentScene.Free();

    // Load a new scene.
    var nextScene = (PackedScene)GD.Load(path);

    // Instance the new scene.
    CurrentScene = nextScene.Instance();

    // Add it to the active scene, as child of root.
    GetTree().GetRoot().AddChild(CurrentScene);

    // Optionally, to make it compatible with the SceneTree.change_scene() API.
    GetTree().SetCurrentScene(CurrentScene);
}

Object.call_deferred() 를 사용한다면 두 번째 기능은 현재 씬의 코드가 모두 종료되었을 때 실행된다. 따라서, 현재 씬은 그것이 사용되고 있을 때 까지 제거되지 않을 것이다.

마지막으로, 두 씬에 비어있는 콜백 함수를 채워야 합니다:

# Add to 'Scene1.gd'.

func _on_Button_pressed():
    Global.goto_scene("res://Scene2.tscn")
// Add to 'Scene1.cs'.

public void OnButtonPressed()
{
    var global = (Global)GetNode("/root/Global");
    global.GotoScene("res://Scene2.tscn");
}

그리고

# Add to 'Scene2.gd'.

func _on_Button_pressed():
    Global.goto_scene("res://Scene1.tscn")
// Add to 'Scene2.cs'.

public void OnButtonPressed()
{
    var global = (Global)GetNode("/root/Global");
    global.GotoScene("res://Scene1.tscn");
}

프로젝트를 실행하고 버튼을 누를 때 씬이 바뀌는지 확인해보세요.

주석

When scenes are small, the transition is instantaneous. However, if your scenes are more complex, they may take a noticeable amount of time to appear. To learn how to handle this, see the next tutorial: Background loading.

Alternatively, if the loading time is relatively short (less than 3 seconds or so), you can display a "loading plaque" by showing some kind of 2D element just before changing the scene. You can then hide it just after the scene is changed. This can be used to indicate to the player that a scene is being loaded.