리소스

노드와 리소스

지금까지 우리는 Godot에서 동작을 코딩하는 데 쓰이고 엔진 대부분의 기능이 의존하는 Node 클래스에 초점을 두었습니다. 이와 마찬가지로 중요한 데이터 타입: Resource가 있습니다.

노드(Node)는 스프라이트, 3D 모델, 물리 시뮬레이션, 유저 인터페이스 정렬 등의 기능을 제공합니다. 리소스(Resource)데이터 컨테이너입니다. 리소스 자체적으로는 아무 것도 하지 않습니다. 대신 노드는 리소스에 포함된 데이터를 사용합니다.

Godot가 디스크에 저장하거나 디스크에서 불러오는 모든 것은 리소스입니다. 씬(.tscn 또는 .scn 파일), 이미지, 스크립트 등이 있습니다. 다음은 몇 가지 리소스의 예입니다: 텍스처(Texture) , 스크립트(Script), 메시(Mesh), 애니메이션(Animation), 오디오스트림(AudioStream), 폰트(Font) , 번역(Translation).

엔진이 디스크에서 리소스를 불러올 때 항상 한 번만 불러옵니다. 해당 리소스의 복사본이 이미 메모리에 있는 경우 리소스를 다시 불러오려고 하면 매번 같은 복사본을 반환합니다. 리소스에는 데이터만 포함되어 있으므로 리소스를 복제할 필요가 없습니다.

노드 또는 리소스와 같은 모든 개체는 속성을 내보낼 수 있습니다. String, integer, Vector2 등과 같은 많은 타입의 속성 중 하나가 리소스가 될 수 있습니다. 즉, 노드와 리소스 모두 리소스를 속성으로 포함할 수 있습니다:

../../_images/nodes_resources.png

외부(External) vs 내장(built-in)

리소스를 저장하는 두 가지 방법이 있습니다. 두 가지 방법은 다음과 같습니다:

  1. 씬의 외부 에 개별 파일로 저장.

  2. .tscn이나 .scn 파일 안에 내장된 채로 저장.

좀 더 구체적으로 말하면, 다음은 Sprite 노드의 Texture입니다:

../../_images/spriteprop.png

리소스 미리보기를 클릭해서 리소스를 보고 속성을 편집할 수 있습니다.

../../_images/resourcerobi.png

Path 속성은 리소스의 경로를 알려줍니다. 이 경우 리소스는 robi.png라는 PNG 이미지에서 가져옵니다. 이와 같이 파일에서 리소스를 불러오는 경우 외부 리소스라고 합니다. Path를 지우거나 비워 두면 내장 리소스가 됩니다.

내장 리소스와 외부 리소스 간의 전환은 씬을 저장할 때 발생합니다. 위의 예시에서 경로 "res://robi.png"를 지우고 저장한다면, Godot는 .tscn 씬 파일 안에 이미지를 저장합니다.

참고

내장 리소스로 저장하더라도 씬을 여러 번 인스턴스화하면 엔진은 해당 복사본을 하나만 불러옵니다.

코드에서 리소스 불러오기

코드에서 리소스를 불러오는 방법은 두 가지가 있습니다. 첫 번째로, 언제든지 load() 함수를 사용해 불러올 수 있습니다:

func _ready():
        var res = load("res://robi.png") # Godot loads the Resource when it reads the line.
        get_node("sprite").texture = res

리소스를 미리 불러올(preload)수도 있습니다. load와는 다르게, 이 함수는 디스크에서 파일을 읽고 컴파일 시간에 파일을 불러옵니다. 결과적으로, 변수 경로로 미리 불러오기를 호출할 수 없고 경로에 상수 문자열을 사용해야 합니다.

func _ready():
        var res = preload("res://robi.png") # Godot loads the resource at compile-time
        get_node("sprite").texture = res

씬 불러오기

씬도 리소스이지만 함정이 있습니다. 씬은 디스크에 PackedScene 타입 리소스로 저장됩니다. 씬은 리소스 안에 압축되어 있습니다.

씬의 인스턴스를 얻으려면 PackedScene.instance() 메서드를 사용해야 합니다.

func _on_shoot():
        var bullet = preload("res://bullet.tscn").instance()
        add_child(bullet)

이 메서드는 씬의 계층 구조에 노드를 만들고, 구성하고, 씬의 루트 노드를 반환합니다. 그런 다음 이것을 다른 노드의 자식으로 추가할 수 있습니다.

이 접근 방식에는 몇 가지 장점이 있습니다. PackedScene.instance() 함수가 빠르기 때문에 매번 디스크에서 다시 불러올 필요 없이 새로운 적, 총알, 효과 등을 만들 수 있습니다. 항상 그렇듯이 이미지, 메시 등은 씬 인스턴스 간에 모두 공유됩니다.

리소스 해제(Free)하기

리소스가 더 이상 사용되지 않으면 자동으로 해제됩니다. 대부분의 경우 리소스는 노드에 포함되어 있기 때문에 노드가 소유한 리소스를 다른 노드에서 사용하지 않는 경우 노드를 해제하면 해당 리소스도 해제합니다.

여러분만의 리소스 만들기

Godot의 어떤 오브젝트나 마찬가지로 유저는 리소스를 스크립트로 작성할 수도 있습니다. 리소스 스크립트는 개체 속성과 직렬화된 텍스트 또는 이진 데이터(/.tres, /.res) 간에 자유롭게 변환하는 기능을 상속받습니다. 또한 참조 타입으로부터 참조 카운팅(reference-counting) 메모리 관리를 상속받습니다.

이는 JSON, CSV 또는 커스텀 TXT 파일과 같은 대체 데이터 구조에 비해 많은 뚜렷한 이점이 있습니다. 사용자는 이러한 애셋을 구문 분석(parse)하기 위해 Dictionary (JSON) 또는 File로만 가져올 수 있습니다. 리소스를 구분하는 것은 Object, Reference, Resource 기능의 상속입니다:

  • 상수를 정의할 수 있으므로 다른 데이터 필드나 오브젝트의 상수가 필요하지 않습니다.

  • 속성에 대한 setter/getter 메서드를 포함한 메서드를 정의할 수 있습니다. 이를 통해 기본 데이터를 추상화하고 캡슐화할 수 있습니다. 리소스 스크립트의 구조를 변경해야 하는 경우 리소스를 사용하는 게임을 변경할 필요가 없습니다.

  • 시그널을 정의할 수 있으므로 리소스가 관리하는 데이터의 변경 사항에 대한 응답을 트리거할 수 있습니다.

  • 정의된 속성이 있으므로 유저는 자신의 데이터가 존재한다는 것을 100% 알 수 있습니다.

  • 리소스 자동 직렬화 및 역직렬화는 Godot 엔진에 내장된 기능입니다. 유저는 리소스 파일의 데이터를 가져오거나 내보내기 위해 사용자 정의 로직을 구현할 필요가 없습니다.

  • 리소스는 하위 리소스를 재귀적으로 직렬화할 수도 있습니다. 이는 유저가 훨씬 더 정교한 데이터 구조를 설계할 수 있음을 의미합니다.

  • 유저는 리소스를 버전 관리에 적합한 텍스트 파일(*.tres)로 저장할 수 있습니다. 게임을 내보낼 때 Godot는 리소스 파일을 바이너리 파일(*.res)로 직렬화해서 속도와 압축률을 높입니다.

  • Godot 엔진의 인스펙터(Inspector)는 리소스 파일을 즉시 렌더링하고 편집합니다. 따라서 사용자는 데이터를 시각화하거나 편집하기 위해 사용자 정의 로직을 구현할 필요가 없는 경우가 많습니다. 이렇게 하려면 파일시스템(FileSystem) 독에서 리소스 파일을 두 번 클릭하거나 인스펙터(Inspector)에서 폴더 아이콘을 클릭하고 대화 상자에서 파일을 여세요.

  • 기본 리소스 외에 다른 리소스 타입을 확장할 수 있습니다.

Godot의 인스펙터(Inspecter)에서는 커스텀 리소스를 쉽게 만들 수 있습니다.

  1. 인스펙터(Inspecter)에서 일반 Resource 오브젝트를 만듭니다. 스크립트가 해당 타입을 확장하는 동안 리소스를 파생하는 타입일 수도 있습니다.

  2. 인스펙터(Inspecter)에서 script 속성을 여러분의 스크립트로 설정합니다.

인스펙터(Inspector)는 이제 여러분의 리소스 스크립트의 사용자 정의 속성을 표시합니다. 해당 값을 편집하고 리소스를 저장하면 인스펙터(Inspector)는 사용자 정의 속성도 직렬화합니다! 인스펙터(Inspector)에서 리소스를 저장하려면 인스펙터(Inspector)의 툴 메뉴(오른쪽 위)를 클릭하고 "저장" 또는 "다른 이름으로 저장..."을 선택하세요.

If the script's language supports script classes, then it streamlines the process. Defining a name for your script alone will add it to the Inspector's creation dialog. This will auto-add your script to the Resource object you create.

몇 가지 예를 살펴보겠습니다.

# bot_stats.gd
extends Resource
export(int) var health
export(Resource) var sub_resource
export(Array, String) var strings

# Make sure that every parameter has a default value.
# Otherwise, there will be problems with creating and editing
# your resource via the inspector.
func _init(p_health = 0, p_sub_resource = null, p_strings = []):
    health = p_health
    sub_resource = p_sub_resource
    strings = p_strings

# bot.gd
extends KinematicBody

export(Resource) var stats

func _ready():
    # Uses an implicit, duck-typed interface for any 'health'-compatible resources.
    if stats:
        print(stats.health) # Prints '10'.

참고

리소스 스크립트는 Unity의 ScriptableObjects와 유사합니다. 인스펙터(Inspector)는 커스텀 리소스를 위한 내장 지원을 제공합니다. 원한다면 유저만의 Control 기반 도구 스크립트를 디자인하고 이를 EditorPlugin과 결합해서 데이터에 대한 커스텀 시각화 및 에디터를 만들 수도 있습니다.

언리얼 엔진 4의 DataTables와 CurveTables도 리소스 스크립트를 사용해 쉽게 다시 만들 수 있습니다. DataTables은 커스텀 구조체에 매핑된 문자열로, 이차적인 커스텀 리소스 스크립트에 문자열을 매핑하는 딕셔너리와 유사합니다.

# bot_stats_table.gd
extends Resource

const BotStats = preload("bot_stats.gd")

var data = {
    "GodotBot": BotStats.new(10), # Creates instance with 10 health.
    "DifferentBot": BotStats.new(20) # A different one with 20 health.
}

func _init():
    print(data)

딕셔너리 값을 인라이닝(inlining)하는 대신에...

  1. 스프레드시트에서 테이블 값을 가져와서 키 값 쌍을 생성할 수 있습니다. 또는...

  2. 에디터 안에서 시각화를 설계하고 해당 타입 리소스를 열 때 인스펙터(Inspecter)에 리소스를 추가하는 간단한 플러그인을 제작할 수 있습니다.

CurveTable은 float 배열 또는 Curve/Curve2D 리소스 개체에 매핑된다는 점을 제외하고는 동일합니다.

경고

리소스 파일(*.tres/*.res)은 파일에서 사용하는 스크립트의 경로를 저장합니다. 리소스를 불러오면 이 스크립트를 해당 타입의 확장으로 가져와 로드합니다. 이는 하위 클래스, 즉 스크립트의 내부 클래스(예: GDScript에서 class 키워드 사용)를 할당할 수 없다는 의미입니다. Godot는 스크립트 서브클래스의 사용자 정의 속성을 적절하게 직렬화하지 않을 것입니다.

아래 예시에서, Godot는 Node 스크립트를 불러오고, Resource를 확장(extend)하지 않는지 확인한 다음, 이 경우 타입이 호환되지 않으므로 스크립트가 Resource 오브젝트를 불러오는 데 실패했다고 판단합니다.

extends Node

class MyResource:
    extends Resource
    export var value = 5

func _ready():
    var my_res = MyResource.new()

    # This will NOT serialize the 'value' property.
    ResourceSaver.save("res://my_res.tres", my_res)