언리얼 오브젝트 처리

UObject 시스템의 기능에 대한 개요입니다.

Choose your operating system:

Windows

macOS

Linux

클래스, 프로퍼티, 함수에 적합한 매크로로 마킹해 주면 UClass, UProperty, UFunction 으로 변합니다. 그러면 언리얼 엔진이 접근할 수 있어, 다수의 내부적인 처리 기능을 구현할 수 있습니다.

자동 프로퍼티 초기화

UObject 는 생성자 호출 전 초기화시 자동으로 0 으로 채워집니다. 클래스, UProperty, 네이티브 멤버 모두에게 전체적으로 벌어지는 일입니다. 그 이후 멤버는 클래스 생성자의 커스텀 값으로 초기화 가능합니다.

레퍼런스 자동 업데이트

AActor 또는 UActorComponent 가 소멸되거나 다른 식으로 플레이에서 제거되면, 리플렉션 시스템에 보이고 있는 그에 대한 모든 ( TArray 같은 언리얼 엔진 컨테이너 클래스에 저장된 포인터와 UProperty 포인터 등의) 레퍼런스는 자동으로 null 이 됩니다. 이는 허상 참조를 예방하여 문제의 소지를 줄인다는 장점이 있지만, 다른 코드 부분에서 AActor UActorComponent 포인터를 소멸시키는 경우 null 이 된다는 것을 뜻하기도 합니다. 여기서 최고의 장점은 null 검사 안정성이 높다는 것인데, 일반적인 null 포인터의 경우와 null 이 아닌 포인터가 삭제된 메모리를 가리키는 경우 둘 다 감지해 내기 때문입니다.

여기서 한 가지 중요한 점은, 이 기능은 UPROPERTY 로 마킹되어 있거나 언리얼 엔진 컨테이너 클래스에 저장된 UActorComponent 또는 AActor 레퍼런스에만 적용된다는 점입니다. raw 포인터에 저장된 오브젝트 레퍼런스는 언리얼 엔진이 알지 못하기 때문에, 자동으로 null 되거나, 가비지 컬렉션이 방지되지 않습니다. 그렇다고 모든 UObject* 변수가 UProperty 가 되어야 한다는 뜻은 아닙니다. UProperty 가 아닌 오브젝트 포인터가 필요한 경우, TWeakObjectPtr 사용을 고려해 보세요. 이는 약 포인터로, 가비지 컬렉션을 방지하지는 않지만, 접근 전 질의를 통해 유효성 검사가 가능하며, 거기서 가리키는 오브젝트가 소멸된 경우 null 설정도 가능합니다.

참조된 UObject UProperty 가 자동으로 null 이 되는 또 한가지 경우는, 에디터에서 애셋을 'Force Delete' (강제 삭제)한 경우입니다. 그에 따라, 애셋인 UObject 에 대한 모든 코드 작업 시 이 포인터가 null 이 되도록 처리해야 합니다.

Serialization

UObject 가 Serialize 될 때, 모든 UProperty 값은 명시적으로 "transient" (휘발성) 마킹 또는 생성자 이후 기본 값에서 미변경 상태가 아닌 이상 자동으로 읽고 쓰기 가능합니다. 예를 들어 레벨에 AEnemy 인스턴스를 배치하고서, 그 Health 를 500 으로 설정하고 저장을 하면, UClass 정의 이외의 코드를 한 줄도 작성하지 않고도 다시 로드할 수 있습니다.

UProperty 가 추가 또는 제거될 때, 기존 콘텐츠 로드는 매끄럽게 처리됩니다. 새 프로퍼티는 새 CDO 에서 기본값을 복사해 옵니다. 제거된 프로퍼티는 말없이 무시됩니다.

커스텀 작동 방식이 필요한 경우, UObject::Serialize 함수를 덮어쓰면 됩니다. 데이터 오류, 버전 번호 검사, 데이터 포맷 변경 시 자동 변환 또는 업데이트 수행 등에 유용하게 쓰일 수 있습니다.

프로퍼티 값 업데이트하기

UClass 의 클래스 디폴트 오브젝트 (CDO) 가 변경되면, 엔진은 그 클래스의 모든 인스턴스 로드시 알아서 변경사항을 적용 시도합니다. 주어진 오브젝트 인스턴스에 대해, 업데이트된 변수 값이 이전 CDO 값과 일치한다면, 새로운 CDO 에 저장된 값으로 업데이트됩니다. 변수 값이 다른 경우, 그 변수가 의도적으로 설정되었다 가정하여 그 변경사항을 보존합니다.

예를 들어 AEnemy 오브젝트를 여럿 배치했고, AEnemy 생성자의 Health 기본값을 100 으로 설정한 레벨을 저장했다 칩시다. 여기서 또, Enemy_3 가 특별히 쎈 놈이라 그 Health 를 500 으로 설정했다 가정합시다. 여기서 마음이 바뀌어 Health 디폴트 값을 150 으로 올렸다고 가정합니다. 다음 번 레벨을 로드할 때, 언리얼은 CDO 를 변경했다고 알아채고, 이전 기본 Health 값(100)인 AEnemy 모든 인스턴스 Health 값을 150 으로 업데이트합니다. Enemy_3 의 Health 는 500 그대로 남아있는데, 이전 기본값이 아니기 때문입니다.

에디터 통합

UObject 와 UProperty 는 에디터에 인식되며, 에디터는 별도의 코드를 작성할 필요 없이 이 값을 자동으로 노출시킬 수 있습니다. 이는 선택적으로 블루프린트 비주얼 스크립팅 시스템으로의 통합(integration)이 가능합니다. 변수와 함수의 노출 및 접근 여부를 제어할 수 있는 옵션이 많이 있습니다.

런타임 유형 정보 및 형변환

UObject 는 언리얼 엔진 리플렉션 시스템의 일부이므로, 항상 자신이 무슨 UClass 인지 알고 있으며, 형변환을 실시간으로 할 수 있습니다.

네이티브 코드에서, 모든 UObject 클래스에는 그 부모 클래스로 설정된 커스텀 "Super" typedef 가 있어, 덮어쓰기 행위에 대한 제어가 쉽게 가능합니다. 예를 들어:

class AEnemy : public ACharacter
{
    virtual void Speak()
    {
        Say("Time to fight!");
    }
};

class AMegaBoss : public AEnemy
{
    virtual void Speak()
    {
        Say("Powering up! ");
        Super::Speak();
    }
};

보시듯이, Speak 를 호출하면 MegaBoss 가 "Powering up! Time to fight!" 라 말하게 됩니다.

또한, 템플릿 Cast 함수를 사용해서 베이스 클래스에서의 오브젝트를 좀더 파생된 클래스로 안전하게 형변환하거나, IsA 를 사용해서 오브젝트가 특정 클래스의 것인지 질의할 수 있습니다. 간단한 예로:

class ALegendaryWeapon : public AWeapon
{
    void SlayMegaBoss()
    {
        TArray<AEnemy> EnemyList = GetEnemyListFromSomewhere();

        // The legendary weapon is only effective against the MegaBoss
        for (AEnemy Enemy : EnemyList)
        {
            AMegaBoss* MegaBoss = Cast<AMegaBoss>(Enemy);
            if (MegaBoss)
            {
                Incinerate(MegaBoss);
            }
        }
    }
};

여기서 Cast 를 사용하여 AEnemy AMegaBoss 로 형변환 시도했습니다. 문제의 오브젝트가 실제로 AMegaBoss (또는 그 자손 클래스)가 아닌 경우, Cast 는 널 포인터를 반환하므로 적절한 대응이 가능합니다. 위 코드에서, MegaBoss 에 대해서는 Incinerate 함수만 호출합니다.

가비지 컬렉션

언리얼에서는 더이상 참조되지 않거나 명시적으로 소멸 예약시킨 UObject 를 주기적으로 정리하는 가비지 컬렉션(garbage collection) 스키마를 사용합니다. 엔진에서는 레퍼런스 그래프를 만들어 어느 오브젝트가 아직 사용중이고 어느 것이 고아가 되었는지를 알아냅니다. 이 그래프 루트에는 "루트 세트"라 지정된 오브젝트 세트가 있습니다. 어떤 오브젝트도 루트 세트에 추가시킬 수 있습니다. 가비지 컬렉션이 발생하면, 엔진은 루트 세트부터 시작해서 알려진 UObject 레퍼런스 트리를 검색하여 참조된 오브젝트를 전부 추적할 수 있습니다. 참조되지 않은 오브젝트, 즉 트리 검색에서 찾지 못한 것들은 더이상 필요치 않은 오브젝트라 가정하고 제거합니다.

여기에 한 가지 숨어있는 실용적인 부분이 있는데, 전형적으로는 살려두고자 하는 오브젝트에 UPROPERTY 레퍼런스를 유지하거나, 그에 대한 포인터를 TArray 또는 다른 언리얼 엔진 컨테이너 클래스에 저장해야 합니다. 종종 액터와 그 컴포넌트는 예외인데, 액터는 보통 자신이 속한 레벨처럼 루트 세트로 다시 링크되는 오브젝트에, 그리고 액터의 컴포넌트는 액터 자체에 레페런싱되기 때문입니다. 액터는 자신의 Destroy 함수를 호출하여 명시적으로 소멸 마킹할 수 있는데, 이는 진행중인 게임에서 액터를 제거하기 위한 표준적인 방식입니다. 컴포넌트는 DestroyComponent 함수로 명시적으로 소멸시킬 수 있으나, 보통은 소유 액터가 게임에서 제거될 때 소멸됩니다.

언리얼 엔진 4 의 가비지 컬렉션은 빠르고 효율적이며, 걸리는 부하를 최소화시키기 위한 기능이 다수 내장되어 있습니다. 예를 들면 고아가 된 오브젝트 식벽을 위한 도달가능성 분석 멀티스레드 처리, 컨테이너에서 액터를 최대한 빠르게 제거할 수 있도록 최적화된 언해시 코드같은 것들입니다. 가비지 컬렉션 방법 및 시점을 보다 정교하게 제어할 수 있는 기능도 프로젝트 세팅 엔진 - 가비지 컬렉션 아래에서 대부분 찾을 수 있습니다. 프로젝트의 가비지 컬렉터 퍼포먼스 튜닝에 흔히 사용되는 세팅은 다음과 같습니다:

세팅

기능 설명

Create Garbage Collector UObject Clusters

가비지 컬렉터 UObject 클러스터 생성 - 프로젝트 세팅에서 켜거나 끌 수 있습니다 (기본으로 켜져 있습니다). 켜면 관련된 오브젝트들을 하나의 가비지 컬렉션 클러스터에 묶어, 오브젝트 각각이 아닌 클러스터 하나만 검사할 수 있도록 해놨습니다. 즉 전체 클러스터를 하나의 오브젝트로 취급할 수 있기에 도달가능성 파악이 빨라지지만, 클러스터의 개별 항목 전부를 같은 프레임에 언해시 및 삭제 준비를 하므로, 클러스터가 너무 클 경우 버벅임이 생길 수 있습니다. 일반적인 경우, 클러스터를 만들면 가비지 컬렉션 퍼포먼스가 향상되며 도달가능성 분석에 소요되는 시간이 단축됩니다.

Merge GC Clusters

GC 클러스터 병합 - 클러스터 병합을 켜면 한 클러스터의 오브젝트가 다른 클러스터의 오브젝트를 참조할 때 클러스터를 합치도록 합니다. 참고로 병합을 유발시킨 레퍼런스를 지워도 새로 병합된 클러스터는 어떤 식으로든 분해되거나 나뉘지 않습니다. 이 기능의 정상 작동을 위해서는 Create Garbage Collector UObject Clusters 옵션도 켜져있어야 합니다. 그러면 가비지 컬렉터의 언해시 및 오브젝트 소멸 작업 빈도를 낮추지만, 더욱 많은 수의 오브젝트를 한꺼번에 언해시 및 소멸시킵니다. 추가로 클러스터 병합을 하지 않았다면 일어났을 가비지 컬렉션이 일어나지 않는 경우가 있을 수 있는데, 클러스터 내 오브젝트로의 레퍼런스가 있으면 전체 클러스터가 가비지 컬렉션 대상에서 제외되기 때문입니다.

Actor Clustering Enabled

액터 클러스터 활성화 - 프로젝트 세팅 에서 이 옵션을 켜고, bCanBeInCluster 변수를 true 로 설정하거나, 코드에서 CanBeInCluster 함수가 true 를 반환하도록 덮어써 주면 액터를 클러스터에 넣을 수 있습니다. 기본적으로, 스태틱 메시 액터와 리플렉션 캡처 컴포넌트를 제외한 액터 및 컴포넌트에는 이 기능이 꺼져있습니다. 이 기능은 한꺼번에 소멸시킬 것으로 예상되는 액터 그룹을 만드는 데 좋은데, 보통 레벨에 배치한 스태틱 메시가 속한 서브레벨을 언로드하지 않고서는 소멸되지 않도록 하는 것입니다.

Blueprint Clustering Enabled

블루프린트 클러스터 활성화 - 블루프린트의 UBlueprintGeneratedClass 및 관련 데이터, 이를테면 공유 UPROPERTY 및 UFUNCTION 데이터같은 것은, 이 세팅을 켜서 클러스터로 묶을 수 있습니다. 여기서 한 가지 중요한 점은, 이렇게 생긴 클러스터는 블루프린트의 개별 인스턴스가 아닌, Blueprint Generated Class 자체를 참조한다는 점입니다.

Time Between Purging Pending Kill Objects

킬 대기중 오브젝트 제거 간격 - 프로젝트 세팅에서 가비지 컬렉션 발동 빈도를 조절할 수 있습니다. 이 하이 레벨 컨트롤은 특히나 버벅임 방지에 좋습니다. 컬렉션 발동 간격을 줄임으로써, 다음 도달가능성 분석 패스에 걸릴 도달불가 오브젝트 발생 가능성을 낮추고, 동시에 너무 많은 액터를 정리하느라 발생할 수 있는 버벅임도 피할 수 있습니다.

ProjectSettingsGarbageCollection.png

프로젝트 세팅 내 가비지 컬렉션 세팅입니다.

네트워크 리플리케이션

UObject 시스템에는 네트워크 통신과 멀티플레이어 게임 을 원활히 하기 위한 탄탄한 함수성 세트가 포함되어 있습니다.

UProperty 에는 태그를 붙여 네트워크 플레이 도중 데이터의 리플리케이트 여부를 엔진에게 알릴 수 있습니다. 여기에 흔히 쓰이는 모델은, 서버에서 변수가 변경되면, 엔진에서 그 변화를 감지하여 모든 클라이언트에 신뢰성있게 전송하는 것입니다. 클라이언트에서는 리플리케이션을 통해 변수가 변할 때, 옵션을 통해 콜백 함수를 받을 수 있습니다.

UFunction 역시도 태그를 붙여 원격 머신에서 실행 시킬 수 있습니다. 예를 들어 "server" 함수는 클라이언트 머신에서 호출시 실제로는 서버 머신에서 해당 액터의 서버 버전이 실행되도록 합니다. 반면 "client" 함수는, 서버에서 호출될 수는 있지만 해당 액터의 소유중인 클라이언트 버전이 실행됩니다.

언리얼 엔진 문서의 미래를 함께 만들어주세요! 더 나은 서비스를 제공할 수 있도록 문서 사용에 대한 피드백을 주세요.
설문조사에 참여해 주세요
건너뛰기