본문 바로가기

레퍼런스/고도엔진

고도엔진 튜토리얼 #13 하나씩 일어나는 일(오토로드) (Singletons (AutoLoad))

도입


씬 싱글톤(하나씩 일어나는 일 - 싱글톤)은 씬 사이에 지속적인 정보를 저장해야 하는 일반적인 사용 사례에 맞게 구성하는데 굉장히 유용합니다.


매우 강력하기는 하지만, 씬 시스템 자체에 몇 가지 단점이 있습니다 :


  • 하나 이상의 씬에서 필요한 정보(플레이어의 아이템 등)를 저장할 공동의 공간이 없습니다.
  • 다른 씬을 로드하고 언로드하기 위한 장면의 경우에는 자식들이 그런 자식 씬에 공통된 정보를 저장하는 것이 가능하지만, 이러한 장면들이 스스로 실행되고 제대로 작동하기를 기대할 수는 없다.
  • 정보가 디스크의 'user://'에 저장될 수 있고 이 정보가 필요한 씬에서 로드될 수 있습니다. 계속해서 씬이 바뀔 때 이 데이터를 저장하고 로드하면 번거롭고 느릴 것입니다.


하지만 고도에서는 여전히 씬의 부분을 만드는 것에 대한 요구가 있습니다 :


  • 어떤 씬이 에디터에 열려 있더라도 항상 로드되어야 합니다.
  • 플레이어의 정보, 아이템, 돈 등 전역 변수를 저장할 수 있고 씬간의 정보 교환이 가능해야 합니다.
  • 씬의 전환과 정보 전달을 다룰 수 있어야 합니다.
  • GDScript가 설계 상 전역 변수를 지원하지 않음에도 싱글톤처럼 행동하게 해야합니디다.


노드와 스크립트를 자동으로 로드하면 이러한 요구 사항을 충족할 수 있습니다.



오토로드


여러분은 씬을 로드하는데에 오토로드를 사용할 수 있습니다. 혹은 노드로부터 상속받은 스크립트를 사용할 수도 있습니다(노드는 만들어질 것이고 스크립트는 노드에 대한 설정을 해줍니다).


씬이나 스크립트를 자동로드하려면, 메뉴에서 Scene > Project Settings를 골라 오토로드 탭으로 바꾸어야 합니다. 목록의 각 엔트리에는 노드의 이름으로 사용된 이름이 제목으로 필요하며, 노드는 언제나 다른 씬이 로드되기 전에 루트 뷰포트에 추가됩니다.


../../_images/singleton.png


이는 어느 노드도 싱글톤에 "플레이어 변수(playervariables)"라는 이름으로 접근할 수 있다는 것입니다 :


var player_vars = get_node("/root/playervariables")



커스텀 씬 전환기


오토로드를 사용해 씬 전환기를 어떻게 만드는 지에 대한 짧은 튜토리얼입니다. 간단한 씬 전환을 위해서는 SceneTree.change_scene() 메소드로 충분하며(SceneTree에서 설명했듯이), 이 메소드는 장면 간을 전환할 때 더 복잡한 동작을 위해 사용됩니다.


먼저 템플릿을 다운로드 받으세요 : 


autoload.zip


그리고 열어주세요.


두 씬이 제공됩니다. 씬 _a.scn과 _b.scn입니다. 각 씬은 동일하고 다른 씬으로 이동하는 버튼을 포함하고 있습니다. 프로젝트가 시작하면 _a.scn에서 시작됩니다. 하지만 버튼을 눌러도 아무런 변화가 없을 겁니다.



global.gd


먼저, global.gd 스크립트를 만드세요. 쉽게 자원을 만드는 방법은 inspector 탭에서 new resource를 누르는 것입니다.


../../_images/newscript.png

global.gd를 스크립트로 저장하세요 :


../../_images/saveasscript.png

스크립트 편집기에서 스크립트를 열 수 있습니다. 다음 단계는 오토로드 목록에 추가하는 것입니다. 메뉴의 Scene > Project Settings를 고른 후, 오토로드 탭으로 전환하고 이 파일을 가리키는 "global"이라는 이름으로 새로운 엔트리를 만드세요.


../../_images/addglobal.png

이제, 여러분의 어떤 씬에서 실행하더라도 스크립트가 로드될 겁니다.


스크립트로 돌아가서, 현재 씬은 _ready() 함수에서 가져올 필요가 있습니다. 현재 씬과 global.gd는 루트의 자식입니다. 하지만 오토로드 되는 노드가 언제나 제일 먼저입니다. 이건 루트의 가장 막내라도 씬에 로드되고 시작할 수 있다는 것을 의미합니다.


참고 : global.gd가 노드에서 확장된 것을 확실히 하세요. 그렇지 않으면 로드되지 않을것입니다!!


extends Node

var current_scene = null

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


다음은 씬을 전환하기 위한 함수입니다. 이 함수는 현재 씬을 반환하고 요청한 씬으로 대체합니다.


func goto_scene(path):

    # This function will usually be called from a signal callback,
    # or some other function from the running scene.
    # Deleting the current scene at this point might be
    # a bad idea, because it may be inside of a callback or function of it.
    # The worst case will be a crash or unexpected behavior.

    # The way around this is deferring the load to a later time, when
    # it is ensured that no code from the current scene is running:

    call_deferred("_deferred_goto_scene",path)


func _deferred_goto_scene(path):

    # Immediately free the current scene,
    # there is no risk here.
    current_scene.free()

    # Load 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)

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

위의 주석에서 말했듯이, 우리는 현재 씬이 사용되면서 삭제되는 상황을 피하고자 했습니다. Object.call_deferred()를 사용하는 것이 필요한 것이죠. 두번째 함수는 현재 씬에서 더 이상 코드가 실행되고 있지 않을 때 나중에 실행될 것입니다.


마지막으로, 남은 것은 빈 _a.gd와 _b.gd를 채우는 것입니다.


#add to scene_a.gd

func _on_goto_scene_pressed():
        get_node("/root/global").goto_scene("res://scene_b.scn")


그리고


#add to scene_b.gd

func _on_goto_scene_pressed():
        get_node("/root/global").goto_scene("res://scene_a.scn")


이제 프로젝트로 돌아가서 실행시켜 보세요. 버튼을 누름으로써 각 씬으로 전환할 수 있을겁니다!


씬을 진행 바와 함께 전환하는 것은 다음 튜토리얼을 확인하세요, 백그라운드 로딩