언리얼에는 게임 오브젝트를 처리하기 위한 강력한 시스템이 있습니다. 언리얼의 오브젝트에 대한 베이스 클래스는 UObject
입니다. UCLASS
매크로는 UObject
에서 파생된 클래스에 태그를 지정하여 UObject 처리 시스템이 인식하도록
할 수 있습니다.
UCLASS 매크로
UCLASS 매크로는 언리얼 기반 타입을 설명하는 UCLASS
에 대한 레퍼런스를 UObject
에 제공합니다. 각 UCLASS
는 클래스 디폴트 오브젝트(Class Default Object, CDO) 라는 하나의 오브젝트를 유지합니다. CDO는 기본적으로 클래스 생성자에 의해 생성되고 이후에는 수정되지 않는 디폴트 '템플릿' 오브젝트입니다.
UCLASS와 CDO는 보통 읽기 전용으로 간주되어야 하지만, 주어진 오브젝트 인스턴스에 대해 얻을 수 있습니다.
오브젝트 인스턴스에 대한 UCLASS는 GetClass()
함수를 사용하여 언제든지 액세스할 수 있습니다.
UCLASS
에는 클래스를 정의하는 프로퍼티와 함수 세트가 포함되어 있습니다. 이는 표준 C++ 코드에 사용할 수 있는 일반 C++ 함수 및 변수이지만, 오브젝트 시스템 내에서 행동하는 방식을 제어하는 언리얼 엔진 전용 메타데이터로 태그가 지정되어 있습니다.
태그 지정 구문에 대한 자세한 내용은 프로그래밍 레퍼런스를 참조하세요.
UObject 클래스에는 UFUNCTION 또는 UPROPERTY 지정자로 리플렉션이 표시되지 않은 네이티브 전용 프로퍼티가 포함될 수 있습니다. 그러나 지정자 매크로로 표시된 함수와 프로퍼티만 해당 UCLASS 내에 나열됩니다.
프로퍼티 및 함수 타입
UObject는 모든 타입의 함수 또는 멤버 변수(프로퍼티라고도 함)를 가질 수 있습니다. 그러나 언리얼 엔진이 이러한 변수나 함수를 인식하고 조작하려면 특수 매크로로 표시하고 특정 타입 표준을 준수해야 합니다. 이러한 표준에 대한 자세한 내용은 프로퍼티 및 UFunctions 레퍼런스 페이지를 참조하세요.
UObject 생성
UObject는 생성자 실행인자를 지원하지 않습니다. 모든 C++ UObject는 엔진 시작 시 초기화되며, 엔진은 디폴트 생성자를 호출합니다. 디폴트 생성자가 없으면 UObject가 컴파일되지 않습니다.
UObject 생성자는 가벼워야 하고 디폴트값과 서브오브젝트를 구성하는 데에만 사용되어야 하며, 생성 시 다른 함수 기능을 호출해서는 안 됩니다. 액터 및 액터 컴포넌트의 경우, 대신에 초기화 함수 기능을 BeginPlay()
메서드에 넣어야 합니다.
UObject는 런타임에 NewObject를 사용하거나 생성자의 경우 CreateDefaultSubobject를 사용하여 생성해야 합니다.
메서드 | 설명 |
---|---|
NewObject<class> |
사용 가능한 모든 생성 옵션에 대한 선택적 파라미터를 사용하여 새 인스턴스를 생성합니다. 이름이 자동으로 생성되는 간단한 사용 사례를 포함하여 유연성이 매우 뛰어납니다. |
CreateDefaultSubobject<class> |
자손 클래스를 생성하고 부모 클래스를 반환하기 위한 메서드를 제공하는 컴포넌트 또는 서브오브젝트를 생성합니다. |
UObject는 절대로 new
연산자를 사용하면 안 됩니다. 모든 UObject는 언리얼 엔진으로 관리되는 메모리이며 가비지 컬렉션됩니다. new 또는 delete를 사용하여 메모리를 수동으로 관리하면 메모리가 손상될 수 있습니다.
Uobject를 통해 제공되는 함수 기능
모든 경우에 이 시스템을 사용하는 것이 필수이거나 적절한 것은 아니지만 사용하면 다음과 같은 많은 이점이 있습니다.
- 가비지 컬렉션
- 레퍼런스 업데이트
- 리플렉션
- 직렬화
- 디폴트 프로퍼티 변경사항의 자동 업데이트
- 자동 프로퍼티 초기화
- 자동 에디터 통합
- 런타임에 사용 가능한 타입 정보
- 네트워크 리플리케이션
이러한 이점 대부분은 UObject와 동일한 리플렉션 및 직렬화 기능을 가진 UStruct에 적용됩니다. UStructs는 값 타입으로 간주되며 가비지 컬렉션되지 않습니다. 각 시스템에 대한 자세한 정보는 언리얼 오브젝트 처리 문서를 참조하세요.
언리얼 헤더 툴
UObject 파생 타입이 제공하는 기능을 활용하려면 해당 타입에 대한 헤더 파일에 전처리 단계를 실행하여 필요한 정보를 대조해야 합니다. 이 전처리 단계는 UnrealHeaderTool, 줄여서 UHT에서 수행합니다. UObject 파생 타입에는 준수해야 하는 특정 구조가 있습니다.
헤더 파일 포맷
소스(.cpp) 파일의 UObject 구현은 다른 C++ 클래스와 같지만, 헤더(.h) 파일의 정의는 언리얼 엔진에서 제대로 작동하려면 특정 기본 구조를 준수해야 합니다. 에디터의 New C++ Class 명령을 사용하는 것은 올바른 포맷의 헤더 파일을 구성하는 가장 쉬운 방법입니다. UObject 파생 클래스의 이름이 UMyObject이고 이 클래스가 생성된 프로젝트가 MyProject라고 할 때 UObject 파생 클래스의 기본 헤더 파일은 다음과 같습니다.
#pragma once
#include 'Object.h'
#include 'MyObject.generated.h'
UCLASS()
class MYPROJECT_API UMyObject : public UObject
{
GENERATED_BODY()
};
언리얼 관련 부분은 다음과 같습니다.
#include "MyObject.generated.h"
이 줄은 파일의 마지막 #include
지시문이 될 것입니다. 이 헤더 파일이 다른 클래스에 대해 알아야 하는 경우 해당 클래스를 파일의 어느 곳에서나 포워드 선언하거나 MyObject.generated.h
위에 포함할 수 있습니다.
UCLASS()
UCLASS
매크로는 언리얼 엔진에 UMyObject
가 표시되도록 합니다. 매크로는 클래스에 대해 어떤 기능을 켜거나 끌지 결정하는 다양한 클래스 지정자를 지원합니다.
class MYPROJECT_API UMyObject : public UObject
MyProject가 UMyObject 클래스를 다른 모듈에 노출시키기를 원한다면 MYPROJECT_API
를 지정해야 합니다. 이는 게임 프로젝트에 포함될 모듈이나 플러그인에 가장 유용하며 여러 프로젝트에 걸쳐 이식 가능하고 자체 포함된 함수 기능을 제공하기 위해 의도적으로 클래스를 노출합니다.
GENERATED_BODY()
GENERATED_BODY
매크로는 실행인자를 사용하지 않지만 엔진에 필요한 인프라를 지원하기 위해 클래스를 구성합니다. 모든 UCLASS
및 USTRUCT
에 필요합니다.
언리얼 헤더 툴은 최소한의 C++ 세트를 지원합니다. UCLASS의 일부를 커스텀 #ifdefs
매크로로 래핑할 때 UHT는 WITH_EDITOR
또는 WITHEDITORONLY_DATA
매크로를 포함하지 않는 매크로를 무시합니다.
오브젝트 업데이트하기
티킹(Ticking)은 언리얼 엔진에서 오브젝트가 업데이트되는 방식을 말합니다. 모든 액터에는 프레임마다 티킹할 수 있는 기능이 있어 필요한 업데이트 계산이나 액션을 수행할 수 있습니다.
액터 및 액터 컴포넌트(ActorComponents)에는 등록 시 자동으로 호출되는 틱(Tick) 함수가 있지만, UObjects
에는 기본 업데이트 기능이 없습니다. 프로젝트에 필요한 경우 상속 클래스 지정자를 사용해 FTickableGameObject
클래스에서 상속하여 추가할 수 있습니다.
그런 다음 엔진이 각 프레임을 호출하는 Tick()
함수를 구현할 수 있습니다.
대부분의 게임 내 오브젝트는 액터이며 프레임당 한 번이 아닌 사용자가 설정한 최소 간격으로 티킹할 수 있습니다.
오브젝트 파괴하기
오브젝트가 더 이상 레퍼런스되지 않을 때 가비지 컬렉션 시스템이 자동으로 오브젝트를 파괴합니다. 즉, UPROPERTY
포인터, 엔진 컨테이너, TStrongObjectPtr
또는 클래스 인스턴스가 강한 참조를 가져서는 안 된다는 뜻입니다.
위크 포인터는 오브젝트가 가비지 컬렉션되는지 여부에 영향을 미치지 않습니다.
가비지 컬렉터를 실행하면, 참조되지 않은 오브젝트를 발견하면 메모리에서 제거합니다. 또한 MarkPendingKill()
함수를 오브젝트에서 바로 호출할 수 있습니다. 이 함수는 오브젝트에 대한 모든 포인터를 NULL
로 설정하고 글로벌 검색에서 오브젝트를 제거합니다. 오브젝트는 다음 가비지 컬렉션 패스에서 완전히 삭제됩니다.
스마트 포인터는 UObject와 함께 사용하기 위한 것이 아닙니다.
-
Object->MarkPendingKill()
은Obj->MarkAsGarbage()
로 대체되었습니다. 이 새로운 함수는 이제 오래된 오브젝트를 추적하는 용도로만 사용됩니다.gc.PendingKillEnabled=true
인 경우PendingKill
로 표시된 오브젝트는 자동으로 null이 되고 가비지 컬렉터에 의해 삭제됩니다. -
강한 참조가 있으면 UObject가 유지됩니다. 이러한 레퍼런스가 UObject를 활성 상태로 유지하는 것을 원하지 않는 경우 해당 레퍼런스가 위크 포인터를 사용하도록 변환하거나 (퍼포먼스가 중요한 경우) 프로그래머가 수동으로 삭제하는 노멀 포인터여야 합니다.
가비지 컬렉션은 프레임 간에만 실행되므로 스트롱 포인터를 위크 포인터로 교체하고 게임플레이 작업 중에 한 번 역레퍼런스할 수 있습니다.
-
IsValid()
는 null인지 가비지인지 확인하기 위한 것이지만 IsValid 사용 사례 대부분은OnDestroy
이벤트가 호출될 때 액터에 대한 포인터를 지우는 것과 같은 적절한 프로그래밍 규칙으로 대체할 수 있습니다. -
PendingKill()
이 비활성화된 경우MarkGarbage()
는 오브젝트의 소유자에게 오브젝트를 파괴하기를 원한다는 플래그를 표시하지만 오브젝트 자체는 해당 오브젝트에 대한 모든 레퍼런스가 해제될 때까지 가비지 컬렉션되지 않습니다. -
액터의 경우, 액터에
Destroy()
가 호출되어 레벨에서 제거되었더라도 해당 액터에 대한 모든 레퍼런스가 해제될 때까지 가비지 컬렉션되지 않습니다. -
라이선시의 경우,
MarkPendingKill()
함수를 사용하여 비용이 높은 오브젝트를 가비지 컬렉션하도록 강제하는 방식이 더 이상 통하지 않는다는 것이 중요한 차이점입니다. -
nullptr에 대한 기존 검사는 수동으로 지우지 않는 경우
IsValid()
호출로 대체해야 합니다. 가비지 컬렉터가 더 이상MarkPendingKill()
을 통해 포인터를 자동으로 지우지 않기 때문입니다.