관리 메뉴

기억을 위한 기록들

[UE4-CPP] [애셋로딩에 관하여] 애셋 참조 방식 2가지(강참조와 약참조) 본문

UnrealEngine/UE4 - CPP

[UE4-CPP] [애셋로딩에 관하여] 애셋 참조 방식 2가지(강참조와 약참조)

에드윈H 2022. 3. 6. 22:50

 

* 언리얼 엔진 4 공식문서 참고하여 정리했습니다.

https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/Assets/ReferencingAssets/

 

애셋 참조

 

docs.unrealengine.com

 

 

언리얼 엔진 4에서는 애셋이 참조되는 방법과 메모리에 로드되는 방법을 제어하는 수많은 메커니즘을 제어한다.

 

레퍼런스는 2가지 방식으로 Strong Reference(강 참조)Weak Reference(약 참조)가 있다.

- 강 참조는 A오브젝트가 B오브젝트를 참조하여 A로드시 B가 로드되게 하는 방식이다.

- 약 참조는 경로(Path)같은 문자열 형태의 간점 메커니즘을 통해 A오브젝트가 B오브젝트를 참조하게 만들어 로드하는 방식이다.

 

 

 

 Strong Reference(강 참조) 유형 1 - 직접 프로퍼티 참조

Ex) 어떤 건물 클래스의 건물지을때 생성되는 소리를 위한 USoundCue 타입이라는 멤버 변수 "ConstructionStartStinger"를 만들어 프로그래머가 아닌, 다른 디자이너가 원하는 사운드를 블루프린트에서 설정할 수 있게 설정하기 위해서이다.

/** construction start sound stinger */

UPROPERTY(EditDefaultsOnly, Category=Building)
USoundCue* ConstructionStartStinger;

UPROPERTY를 통해 EditDefaultsOnly키워드로  에디터에서 노출 및 제어한다.

 

해당 클래스를 상속받는 블루프린트 클래스를 새로 만들면 디자이너가 원하는 사운드를 블루프린트용으로 저장 가능하고, 디자이너가 만든 블루프린트가 로드 될때마다 UPROPERTY의 일부로 참조되는 사운드 역시 자동으로 로드된다.

 

 Strong Reference(강 참조) 유형 2 - 생성 시간에 참조

Ex) 로드해야하는 애셋의 경로를 프로그래머가 정확히 알고 있고, 해당 오브젝트 생성 시 일부분으로 설정하는 경우. ConstructorHelpers라는 특수클래스가 사용되고, 클래스 생성 단계 도 중 오브젝트 혹은 오브젝트 클래스를 찾게 해준다.

/** gray health bar texture */

UPROPERTY()

class UTexture2D* BarFillTexture;

AStrategyHUD::AStrategyHUD(const FObjectInitializer& ObjectInitializer) :
    Super(ObjectInitializer)
{
    static ConstructorHelpers::FObjectFinder<UTexture2D> BarFillObj(TEXT("/Game/UI/HUD/BarFill"));

    ...

    BarFillTexture = BarFillObj.Object;

    ...

}

ConstructorHelpers클래스는 메모리에서 애셋을 찾아본 다음 없으면, 로드하지 않는다. 로드할 수 없는 경우는 nullptr로 설정된다. 그럴 경우 위 코드 텍스쳐에 접근하려는 경우 크래시가 발생한다.

 

 

- 필요에 따라 데이터를 로드 및 참조에 사용되는 방법은 일반적으로 두 가지 있다.

 

 

위의 2가지  Strong Reference(강 참조) 2가지 방법은 초기 설정되는 부분만 빼면 동일하게 작동.

 

한 가지 고려할 점이라고 하면, 당장 사용하지 않더라도 오브젝트가 로드 및 초기화된다면 강 참조된 이셋로 로드된다는 점이다...(당장 사용하지 않더라도 다수의 애셋이 한꺼번에 로드되어 메모리가 눈덩이처럼 불어난다.)

그 로딩을 바로 해버리지 않고, 필요한 부분의 실행시간에 로드할 부분을 나눈다면, 약 참조가 도움이 되고 오히려 더 나을 수 있다.

 

 

 

Weak Reference(약 참조) - 간접 프로퍼티 참조

애셋 로드를 쉽게 제어할 수 있는 방법은 TSoftObjectPtr를 사용하는 것이다.

TSoftObjectPtr를 사용하려면 애셋을 수동으로 로드해야 하는 2가지 방법으로 동기식 방법비동기식 방법이 있다.

1.  동기식 방법 : 템플릿으로 된 LoadObject <>() 메서드나, StaticLoadObject() 메서드
2.  비동기식 방법 : FStreamableManager 사용할 수 있다.

 예시)

/.h

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category=Building)
TSoftObjectPtr<UStaticMesh> BaseMesh;

//.cpp

UStaticMesh* GetLazyLoadedMesh()
{
    if (BaseMesh.IsPending())
    {
        const FSoftObjectPath& AssetRef = BaseMesh.ToStringReference();
        BaseMesh = Cast< UStaticMesh>(FStreamableManager::LoadSynchronous(AssetRef));
    }
    return BaseMesh.Get();
}

코드 설명) TSoftObjectPtr를 사용하여 런타임에 메시를 천천히(Lazy) 로드하고 있다.  애셋을 검사해서 객체가 로드되었는지 확인하고, 로드되지 않은 경우, 동기성 로드 FStreamableManager의 LoadSynchronouse 함수를 사용하고 TSoftObjectPtr안의 UStaticMesh포인터(Get() 함수)를 return 한다.

 

애셋 유형 대신에 UClass 로드를 연기하기 원한다면, TSoftObjectPtr 템플릿 유형 클래스 전용 버전인 TSoftClassPtr와 같은 템플릿유형을 사용할 수 있습니다. 이 함수는 지정된 애셋을 참조하는 것과 같은 역할을 하지만, 인스턴스 대신 애셋에 대한 UClass를 참조한다.

그 예로 바로 애니메이션 블루프린트가 있다. UAnimInstance를 상속받고 있긴 하지만, 애니메이션 블루프린트를 만들어 애님 이벤트와 애님 그래프를 무조건 사용해야 한다. 그러므로 UAnimInstance 즉 애니메이션 블루프린트 애셋 같은 경우는 TSoftClassPtr를 사용해야 정상적으로 가져올 수 있다.
	// .h 
    
    UPROPERTY( EditDefaultsOnly, BlueprintReadOnly )
	TSoftClassPtr<UAnimInstance> AnimClass;

 

Weak Reference(약 참조) - 객체(Object) 검색/로드

간접 프로퍼티 참조는 UPROPERTY 기반으로 에디터에서 지정해주었고, 코드에서 애셋경로인 String으로 런타임에 만들어 그 객체로의 레퍼런스를 구하는 방법으로

1. 이미 생성돼있거나 로드되어 있는 UObject만 사용하려는 경우 :  FindObject <>()

2. 이미 로드되어있지 않는 오브젝트를 로드하려면 는 경우 :  LoadObject <>()

내부적으로는 FindObject와 동일한 역할을 하므로, 오브젝트를 먼저 검색한 다음 로드 할 필요가 없다.

AFunctionalTest* TestToRun = FindObject<AFunctionalTest>(TestsOuter, *TestName);
GridTexture = LoadObject<UTexture2D>(NULL, TEXT("/Engine/EngineMaterials/DefaultWhiteGrid.DefaultWhiteGrid"), NULL, LOAD_None, NULL);

UClass 로드를 위한 LoadClass가 있다.

DefaultPreviewPawnClass = LoadClass<APawn>(NULL, *PreviewPawnName, NULL, LOAD_None, NULL);

결국 위의 코드도 결국 LoadObject로 가능하다.

DefaultPreviewPawnClass = LoadObject<UClass>(NULL, *PreviewPawnName, NULL, LOAD_None, NULL);

if (!DefaultPreviewPawnClass->IsChildOf(APawn::StaticClass()))
{
    DefaultPreviewPawnClass = nullptr;
}