[언리얼엔진] TSoftObjectPtr::IsValid

시작하며

에디터 모드(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 함수로 체크하자.

반응형

댓글

Designed by JB FACTORY