TObjectPtr, TSoftObjectPtr, TWeakObjectPtr 모두 UObject를 참조하기 위한 스마트 포인터이다.
개발 중 위 스마트 포인터들의 차이점을 모르고 사용하다 몇 시간 동안 에러로 고생해서 글로 남깁니다. 😱😱
에디터/런타임에서 이 포인터가 가리키는 에셋은 Reference Graph에 걸린다.
그래서 액터가 로드될 때, 액터의 멤버변수로 TObjectPtr이 있다면 같이 로드된다.
액터에 반드시 존재해야하는 필수적인 에셋을 관리한다.
내부적으로 에셋 경로(FSoftObjectPath)를 들고 있다. 때문에 세이브 파일/블루프린트/에디터 에셋 필드에 적합하다.
액터가 로드되어도 직접 로드하기 전까지는 메모리에 올리지 않는다.
메모리를 아끼고 필요할 때만 로드하여 메모리 절약 및 초기 로딩 최적화에 유리하다. 대신 로드 코드를 직접 작성해야한다.
에셋이 아직 로드되어있지 않으면 .Get()을 사용해도 nullptr이 나온다.
실제 객체가 필요할 때 LoadSynchronous()로 로드 후 Get()으로 가져와야 한다.
UPROPERTY(EditDefaultsOnly, Category="Input")
TSoftObjectPtr<UInputMappingContext> DefaultIMC;
UInputMappingContext* IMC = DefaultIMC.LoadSynchronous();
순환 참조 문제를 해결하기 위한 포인터
GC가 객체를 수거하면 자동으로 nullptr로 처리할 수 있다.
개발하던 중 알 수 없는 오류가 발생했다.
캐릭터에 입력 기능을 만들고 이동을 테스트하는데 어떤 때는 움직이고 어떤 때는 움직이지 않았다.
이것 저것 살펴보던 중 에디터를 실행한 후 IMC 에셋을 로드한 상태에선 IMC가 캐릭터에 들어가서 입력이 되었으나 IMC 에셋을 로드하지 않은 상태에선 IMC가 들어가지 않는 것을 확인했다.
USTRUCT()
struct FInputMappingContextAndPriority
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category = "Input", meta = (AssetBundles = "Client,Server"))
TObjectPtr<UInputMappingContext> InputMapping;
// Higher priority input mappings will be prioritized over mappings with a lower priority.
UPROPERTY(EditAnywhere, Category = "Input")
int32 Priority = 0;
};
IMC와 Priority를 담은 구조체를 만들었고 여기서 IMC는 TSoftObject 스마트 포인터로 보관했다.
나중에 IMC를 가져올 때 그냥 Get으로 가져왔고 여기서 문제가 발생했다.
for (const FInputMappingContextAndPriority& Mapping : DefaultInputMappings)
{
if (UInputMappingContext* IMC = Mapping.InputMapping.Get()) // 이 부분
{
if (UEnhancedInputUserSettings* Settings = Subsystem->GetUserSettings())
{
Settings->RegisterInputMappingContext(IMC);
}
Subsystem->AddMappingContext(IMC, Mapping.Priority);
}
}
IMC를 TSoftObjectPtr로 관리하면서 IMC가 메모리에 없고 nullptr이 나오기 때문에 입력이 먹히지 않았다.
IMC 에셋을 연 이후에는 에셋을 열면서 강제로 로드되고 그제서야 동작이 되었던 것이다.
위 코드는 Lyra 예제에서 가져온건데 IMC를 TSoftObjectPtr로 관리한 이유는 여러 IMC를 전부 로드해서 메모리에 올리지 않고 필요할 때만 로드해서 적용하려는 목적인 것 같다.
아무튼 이를 해결하기 위해 LoadSynchronous() 함수를 추가하거나 그냥 TObjectPtr을 쓸 수도 있다.