시작하며
에디터 모드(PIE)에서는 정상적으로 동작되었지만 릴리즈 모드 에서는 아이콘이 적용되지 않는 이슈가 발생하였다. 따라서 해당 이슈 분석 결과를 기록하고자 한다.
에디터모드 vs 릴리즈모드에 따른 SoftObjectPtr의 IsValid 함수 사용 이슈
- TSoftObjectPtr를 이용하여 애셋을 참조하는 경우 다음과 같이 조건 처리하였다.
TSoftObjectPtr<UObject> WeaponIcon = GetWeaponIcon(WeaponType);
if( WeaponIcon.IsValid())
{
FString IconPath = WeaponIcon.ToString();
//IconPath로 아이콘 리소스 교체 중략
}
- 위 코드 결과 TSoftObjectPtr의 IsValid 함수 사용 시 모드에 따라 결과값이 달라 이슈가 발생하였다.
- 에디터 모드(PIE)는 true / 릴리즈 모드에서는 false가 반환
TSoftObjectPtr::IsValid()
- IsValid함수 구현부는 다음과 같다
- Get()함수를 호출하여 null 체크를 진행한다.
template<class TClass=UObject>
class TSoftClassPtr
{
...
/**
* Test if this points to a live UObject
*
* @return true if Get() would return a valid non-null pointer
*/
FORCEINLINE bool IsValid() const
{
// This does the runtime type check
return Get() != nullptr;
}
...
}
- Get함수를 들어가보자.
- TSoftObjectPtr의 FSoftObjectPtr 타입 멤버 변수 SoftObjectPtr의 Get함수를 반환하고 있다.
template<class TClass=UObject>
class TSoftClassPtr
{
...
/**
* Dereference the soft pointer.
*
* @return nullptr if this object is gone or the lazy pointer was null, otherwise a valid UObject pointer
*/
FORCEINLINE T* Get() const
{
return dynamic_cast<T*>(SoftObjectPtr.Get());
}
...
}
에디터 환경에서 FSoftObjectPtr의 Get
- FSoftObjectPtr의 Get함수가 WITH_EDITOR 로 감싸져 있다.
- 에디터 환경에서는 (아마도?) 내부에서 애셋을 로드해주고 유효한 포인터를 반환하는 것으로 추정된다.
struct FSoftObjectPtr : public TPersistentObjectPtr<FSoftObjectPath>
{
...
#if WITH_EDITOR
/** Overridden to deal with PIE lookups */
FORCEINLINE UObject* Get() const
{
if (GPlayInEditorID != INDEX_NONE)
{
// Cannot use or set the cached value in PIE as it may affect other PIE instances or the editor
TWeakObjectPtr<UObject> Result = GetUniqueID().ResolveObject();
// If this object is pending kill or otherwise invalid, this will return nullptr just like TPersistentObjectPtr<FSoftObjectPath>::Get()
return Result.Get();
}
return TPersistentObjectPtr<FSoftObjectPath>::Get();
}
#endif
...
}
릴리즈 환경에서 FSoftObjectPtr의 Get
- 릴리즈모드에서는 FSoftObjectPtr의 부모클래스인 TPersistentObjectPtr의 Get함수가 호출한다
- 저 검사를 하고 Path를 받아 로드를 하는데 당연히 아직 로드되기 전이기 때문에 nullptr이 반환된다.
- 언리얼 문서의 애셋 참조 페이지를 보면
TSoftObjectPtr 을 사용하려면 애셋을 수동으로 로드해야 합니다.
라고 적혀있다.
template<class TObjectID>
struct TPersistentObjectPtr
{
...
/**
* Dereference the pointer, which may cause it to become valid again. Will not try to load pending outside of game thread
*
* @return nullptr if this object is gone or the pointer was null, otherwise a valid UObject pointer
*/
FORCEINLINE UObject* Get() const
{
UObject* Object = WeakPtr.Get();
// Do a full resolve if the returned object is null and either we think we've loaded new objects, or the weak ptr may be stale
if (!Object && ObjectID.IsValid() && (TObjectID::GetCurrentTag() != TagAtLastTest || !WeakPtr.IsExplicitlyNull()))
{
Object = ObjectID.ResolveObject();
WeakPtr = Object;
// Not safe to update tag during save as ResolveObject may have failed accidentally
if (Object || !GIsSavingPackage)
{
TagAtLastTest = TObjectID::GetCurrentTag();
}
// If this object is pending kill or otherwise invalid, this will return nullptr as expected
Object = WeakPtr.Get();
}
return Object;
}
...
}
이슈 해결
- 에디터 모드와 릴리즈 모드 양쪽에서 애셋 참조 유효성을 체크하려면 IsValid 가 아니라 IsNull 로 체크해야 한다.
TSoftObjectPtr<UObject> WeaponIcon = GetWeaponIcon(WeaponType);
if( WeaponIcon.IsNull())
{
FString IconPath = WeaponIcon.ToString();
//IconPath로 아이콘 리소스 교체 중략
}
요약
TSoftObjectPtr에 데이터가 들어있는지 확인하려면 IsValid가 아닌 IsNull 함수로 체크하자.
반응형