Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

객체 클래스

더 보기

이 페이지는 Godot에서 객체의 C++ 구현을 설명합니다. 객체 클래스 참조를 찾고 계십니까? 여기를 살펴보세요.

일반 정의

:ref:`Object <class_object>`는 거의 모든 것에 대한 기본 클래스입니다. Godot의 대부분의 클래스는 직접 또는 간접적으로 상속받습니다. 이를 선언하려면 다음과 같은 단일 매크로를 사용하면 됩니다.

class CustomObject : public Object {
    GDCLASS(CustomObject, Object); // This is required to inherit from Object.
};

객체에는 반사 및 편집 가능한 속성과 같은 다양한 기능이 내장되어 있습니다.

CustomObject *obj = memnew(CustomObject);
print_line("Object class: ", obj->get_class()); // print object class

OtherClass *obj2 = Object::cast_to<OtherClass>(obj); // Converting between classes, similar to dynamic_cast

참고자료:

스크립트를 클래스로 등록하기

대부분의 Object 서브클래스는 ``GDREGISTER_CLASS``를 호출하여 등록됩니다.

GDREGISTER_CLASS(MyCustomClass)

그러면 클래스가 스크립트, 코드 또는 역직렬화를 통해 인스턴스화될 수 있도록 ``ClassDB``에 명명된 공용 클래스로 등록됩니다. ``GDREGISTER_CLASS``로 등록된 클래스는 예를 들어 편집기나 문서 시스템에 의해 자동으로 인스턴스화되거나 해제되어야 합니다.

GDREGISTER_CLASS 외에도 몇 가지 다른 비공개 모드가 있습니다.

// Registers the class publicly, but prevents automatic instantiation through ClassDB.
GDREGISTER_VIRTUAL_CLASS(MyCustomClass);

// Registers the class publicly, but prevents all instantiation through ClassDB.
GDREGISTER_ABSTRACT_CLASS(MyCustomClass);

// Registers the class in ClassDB, but marks it as private,
// such that it is not visible to scripts or extensions.
// This is the same as not registering the class explicitly at all
// - in this case, the class is registered as internal automatically
// when it is first constructed.
GDREGISTER_INTERNAL_CLASS(MyCustomClass);

// Registers the class such that it is only available at runtime (but not in the editor).
GDREGISTER_RUNTIME_CLASS(MyCustomClass);

GDCLASS(MyCustomClass, SuperClass) 대신 ``GDSOFTCLASS(MyCustomClass, SuperClass)``를 사용하는 것도 가능합니다. 이렇게 정의된 클래스는 ``ClassDB``에 전혀 등록되지 않습니다. 이는 때때로 플랫폼별 하위 클래스에 사용됩니다.

스크립트를 클래스로 등록하기

개체 파생 클래스는 정적 함수 ``static void _bind_methods()``를 재정의할 수 있습니다. 클래스가 등록되면 이 정적 함수가 호출되어 모든 객체 메서드, 속성, 상수 등을 등록합니다. 이 함수는 한 번만 호출됩니다.

_bind_methods 내에서 수행할 수 있는 몇 가지 작업이 있습니다. 기능 등록은 다음 중 하나입니다.

ClassDB::bind_method(D_METHOD("methodname", "arg1name", "arg2name", "arg3name"), &MyCustomType::method);

인수의 기본값은 마지막에 매개변수로 전달될 수 있습니다.

ClassDB::bind_method(D_METHOD("methodname", "arg1name", "arg2name", "arg3name"), &MyCustomType::method, DEFVAL(-1), DEFVAL(-2)); // Default values for arg2name (-1) and arg3name (-2).

기본값은 선언된 순서와 동일하게 제공되어야 하며 필수 인수는 건너뛰고 선택적 인수에는 기본값을 제공해야 합니다. 이는 C++에서 메서드를 선언하는 구문과 일치합니다.

``D_METHOD``는 효율성을 높이기 위해 "methodname"을 StringName으로 변환하는 매크로입니다. 인수 이름은 자체 검사에 사용되지만 릴리스 시 컴파일할 때 매크로는 이를 무시하므로 문자열은 사용되지 않고 최적화됩니다.

더 많은 예제를 보려면 컨트롤 또는 개체의 ``_bind_methods``를 확인하세요.

철저하게 문서화될 것으로 예상되지 않는 모듈과 기능을 추가하는 경우 D_METHOD() 매크로는 안전하게 무시될 수 있으며 간결성을 위해 이름을 전달하는 문자열이 전달될 수 있습니다.

참고자료:

상수

클래스에는 다음과 같은 열거형이 있는 경우가 많습니다.

enum SomeMode {
   MODE_FIRST,
   MODE_SECOND
};

메서드에 바인딩할 때 이러한 기능이 작동하려면 열거형을 int로 변환할 수 있도록 선언해야 합니다. 이를 돕기 위해 매크로가 제공됩니다.

VARIANT_ENUM_CAST(MyClass::SomeMode); // now functions that take SomeMode can be bound.

다음을 사용하여 상수를 _bind_methods 내부에 바인딩할 수도 있습니다.

BIND_CONSTANT(MODE_FIRST);
BIND_CONSTANT(MODE_SECOND);

속성(설정/가져오기)

개체 내보내기 속성, 속성은 다음에 유용합니다.

  • 개체를 직렬화 및 역직렬화합니다.

  • 객체 파생 클래스에 대해 편집 가능한 값 목록을 만듭니다.

속성은 일반적으로 PropertyInfo() 클래스에 의해 정의되고 다음과 같이 구성됩니다.

PropertyInfo(type, name, hint, hint_string, usage_flags)

예를 들면:

PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "0,49,1", PROPERTY_USAGE_EDITOR)

이는 "amount"라는 정수 속성입니다. 힌트는 범위이며 범위는 1(정수) 단위로 0부터 49까지입니다. 편집기(값을 시각적으로 편집)에만 사용할 수 있지만 직렬화되지는 않습니다.

또 다른 예:

PropertyInfo(Variant::STRING, "modes", PROPERTY_HINT_ENUM, "Enabled,Disabled,Turbo")

이것은 문자열 속성이며 모든 문자열을 사용할 수 있지만 편집기에서는 정의된 힌트 문자열만 허용합니다. 사용 플래그가 지정되지 않았으므로 기본 플래그는 PROPERTY_USAGE_STORAGE 및 PROPERTY_USAGE_EDITOR입니다.

object.h에는 많은 힌트와 사용 플래그가 있으므로 확인해 보세요.

속성은 C# 속성처럼 작동할 수도 있고 인덱싱을 사용하여 스크립트에서 액세스할 수도 있지만 가독성을 위해 함수를 사용하는 것이 선호되므로 일반적으로 이러한 사용은 권장되지 않습니다. 많은 속성은 연산자 []를 사용하지 않는 한 인덱싱을 불가능하게 만드는 "애니메이션/프레임"과 같은 범주와도 바인딩됩니다.

``_bind_methods()``부터 설정/가져오기 기능이 존재하는 한 속성을 생성하고 바인딩할 수 있습니다. 예:

ADD_PROPERTY(PropertyInfo(Variant::INT, "amount"), "set_amount", "get_amount")

그러면 setter와 getter를 사용하여 속성이 생성됩니다.

_set/_get/``_get_property_list``를 사용하는 바인딩 속성

더 많은 유연성이 필요한 경우(예: 컨텍스트에 따라 속성 추가 또는 제거) 속성을 생성하는 추가 방법이 있습니다.

다음 함수는 객체 파생 클래스에서 재정의될 수 있습니다. 가상이 아니며 가상으로 만들지 마세요. 재정의할 때마다 호출되며 이전 함수는 무효화되지 않습니다(다단계 호출).

protected:
     void _get_property_list(List<PropertyInfo> *r_props) const;      // return list of properties
     bool _get(const StringName &p_property, Variant &r_value) const; // return true if property was found
     bool _set(const StringName &p_property, const Variant &p_value); // return true if property was found

또한 ``p_property``는 원하는 이름과 일련 순서로 비교해야 하므로 효율성이 약간 떨어집니다.

시그널

개체에는 정의된 시그널 집합이 있을 수 있습니다(다른 언어의 대리인과 유사). 이 예에서는 연결 방법을 보여줍니다.

// This is the function signature:
//
// Error connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags = 0)
//
// For example:
obj->connect("signal_name_here", callable_mp(this, &MyCustomType::method), CONNECT_DEFERRED);

callable_mp``는 멤버 함수에 대한 사용자 정의 호출 가능 함수 포인터를 생성하는 매크로입니다. ``p_flags 값은 :ref:`ConnectFlags <enum_Object_ConnectFlags>`을 참조하세요.

클래스에 시그널를 추가하는 작업은 _bind_methods``에서 ``ADD_SIGNAL 매크로를 사용하여 수행됩니다. 예:

ADD_SIGNAL(MethodInfo("been_killed"))

형 변환과 캐스트하기

객체는 힙에 할당됩니다. 두 가지 소유권 모델이 있습니다.

  • ``RefCounted``에서 파생된 개체는 참조 계산됩니다.

  • 다른 모든 객체는 수동으로 메모리를 관리합니다.

소유권 모델은 근본적으로 다릅니다. 객체를 생성, 저장 및 해제하는 방법을 알아보려면 각각에 대한 섹션을 참조하세요.

``Object *``를 통해 전달된 객체가 ``RefCounted``인지 여부를 모르고 이를 저장해야 하는 경우 포인터 대신 ``ObjectID``를 저장해야 합니다(아래 설명과 같이 수동 메모리 관리 섹션 참조).

:ref:`Variant<class_Variant>`를 통해 객체가 전달될 때, 특히 지연된 콜백을 사용하는 경우, 함수가 실행될 때 포함된 ``Object *``가 이미 해제되었을 가능성이 있습니다. ``Object *``로 직접 변환하는 대신 ``get_validated_object``를 사용해야 합니다.

void do_something(Variant p_variant) {
    Object *object = p_variant.get_validated_object();
    ERR_FAIL_NULL(object);
}

메모리 관리

수동으로 메모리 관리되는 개체는 ``memnew``를 사용하여 생성되고 ``memdelete``를 사용하여 해제됩니다.

Node *node = memnew(Node);
// ...
memdelete(node);
node = nullptr;

개체의 유일한 소유자가 아닌 경우 해당 개체에 대한 포인터를 저장하는 것은 위험합니다. 개체는 언제든지 해당 개체에 대한 다른 참조를 통해 해제될 수 있으며 이로 인해 포인터가 매달린 포인터가 되어 결국 충돌이 발생할 수 있습니다.

당신이 유일한 소유자가 아닌 객체를 저장할 때 포인터 대신 ``ObjectID``를 저장해야 합니다.

Node *node = memnew(Node);
ObjectID node_id = node.get_instance_id();
// ...
Object *maybe_node = ObjectDB::get_instance(node_id);
ERR_FAIL_NULL(maybe_node); // The node may have been freed between calls.

메모리 관리

RefCounted 하위 클래스는 `참조 계산 의미론 <https://en.wikipedia.org/wiki/Reference_counting>`__으로 메모리 관리됩니다.

이는 memnew``를 사용하여 구성되며 ``Ref 인스턴스에 저장되어야 합니다. 마지막 Ref 인스턴스가 삭제되면 개체가 자동으로 자체 소멸됩니다.

class MyRefCounted: public RefCounted {
    GDCLASS(MyReference, RefCounted);
};

Ref<MyRefCounted> my_ref = memnew(MyRefCounted);
// ...
// Ref holds shared ownership over the object, so the object
// will not be freed. As long as you have a valid, non-null
// Ref, it can be safely assumed the object is still valid.
my_ref->get_class_name();

다른 소유자가 있을 수 있으므로 RefCounted 하위 클래스에 대해 ``memdelete``를 호출하면 안 됩니다.

또한 원시 포인터(예: RefCounted *object = memnew(RefCounted))를 사용하여 RefCounted 하위 클래스를 저장해서는 안 됩니다. 다른 소유자가 개체를 파괴하여 매달린 포인터가 남게 되어 결국 충돌이 발생할 수 있으므로 이는 안전하지 않습니다.

참고자료:

동적 캐스팅

Godot는 객체 파생 클래스 간의 동적 캐스팅을 제공합니다. 예를 들면 다음과 같습니다:

void some_func(Object *p_object) {
     Button *button = Object::cast_to<Button>(p_object);
}

캐스트가 실패하면 ``nullptr``가 반환됩니다. 이는 ``dynamic_cast``와 동일하게 작동하지만 `C++ RTTI <https://en.wikipedia.org/wiki/Run-time_type_information>`__을 사용하지 않습니다.

알림(Notification)

Godot의 모든 객체에는 _notification 메서드가 있어 관련될 수 있는 엔진 수준 콜백에 응답할 수 있습니다. 자세한 내용은 알림(Notifications) 페이지에서 확인할 수 있습니다.

리소스

:ref:`Resource <class_resource>`은 RefCounted에서 상속되므로 모든 리소스는 참조 계산됩니다. 리소스에는 선택적으로 디스크의 파일을 참조하는 경로가 포함될 수 있습니다. 이는 일반적으로 리소스 로더에 의해 수행되지만 ``resource.set_path(path)``로 설정할 수 있습니다. 서로 다른 두 리소스는 동일한 경로를 가질 수 없습니다. 그렇게 하면 오류가 발생합니다.

경로가 없는 리소스도 괜찮습니다.

참고자료:

리소스 로딩

다음과 같이 ResourceLoader API를 사용하여 리소스를 로드할 수 있습니다.

Ref<Resource> res = ResourceLoader::load("res://someresource.res")

If a reference to that resource has been loaded previously and is in memory, the ResourceLoader will return that reference. This means that there can be only one resource loaded from a file referenced on disk at the same time.

참고자료:

자원 절약

리소스 절약 API를 사용하여 리소스를 저장할 수 있습니다.

ResourceSaver::save("res://someresource.res", instance)

인스턴스가 저장되고, 파일 경로가 있는 하위 리소스가 해당 리소스에 대한 참조로 저장됩니다. 경로가 없는 하위 리소스는 ``res://someresource.res::1``와 같이 저장된 리소스 및 할당된 하위 ID와 함께 묶입니다. 이는 로드할 때 캐시하는 데도 도움이 됩니다.

참고자료: