주의 : 공부하면서 정리한 것이라 잘못된 내용이 포함될 수 있습니다.


 

1. 들어가며

 

UE4 와 관련한 프로그래밍을 하면서 처음에 가장 헷갈리는 부분이 패키지와 애셋이라는 개념입니다. 그리고 이것은 나중에 C++ 코드를 통해 패키지나 애셋을 로드하다가 보면 머리가 아파집니다. 경로를 집어 넣으라고 하는데 뭐가 뭔지 알수가 없습니다.

 

사실 언리얼 공식 문서인 [ 애셋과 패키지 ] 에 그 개념들에 대해서 설명을 하고 있기는 하지만 좀 부족합니다. 아니 부족하다기 보다는 우리가 생각하는 애셋과 패키지가 아닙니다. 뭐랄까 아티스트나 디자이너 위주의 추상화된 개념이라 할 수 있습니다. 실제 API 들에서 명명하는 개념과는 다릅니다.

 

특히나 커맨드렛 같은 도구를 통해 애셋관리를 자동화할 때 이 문제는 심각해집니다. 애셋을 복사하고 싶은데 애셋을 복사하는 방법을 모릅니다. 그래서 어찌어찌 내가 애셋을 복사한다고 하는게 패키지를 복사하는 것이라는 것을 깨닫게 됩니다. 그런데 패키지를 복사한 후에 경로 문제 때문에 애셋을 로드하지 못하는 사태가 발생합니다.

 

많은 분들이 이런 문제들 때문에 골치가 아팠을 것이라 생각합니다. 그래서 이 문서에서는 그런 개념들에 대해 정리해 볼 계획입니다. 모쪼록 도움이 됐으면 좋겠네요.

 

2. 패키지와 애셋

 

여러분은 패키지라고 하면 무엇이 생각나십니까? 여러 개의 애셋을 하나로 묶어 놓은 ( Unity3D 에서와 같은 ) 번들을 생각하시는 분도 있을테고 배포하기 위해서 묶어 놓은 패키지를 생각하실 수 있을 것입니다. UE4 에디터에서 공식적으로 패키지라는 이름이 등장하는 것은 후자의 경우입니다. 쿠킹 패키징할 때의 패키지입니다.

 

 

하지만 코딩 문맥에서는 패키지라는 것은 그냥 "*.uasset" 파일입니다. UE3 에서는 어땠는지 모르겠지만 UE4 에서는 패키지와 애셋은 일반적으로 1:1 로 대응됩니다. 그럼 ".uasset" 파일을 칸텐츠 브라우저에서는 애셋이라 부르고 코드에서는 패키지라 부르는 것일까요? 여러분은 어떻게 생각하시나요?

 

아래의 칸텐츠 브라우저에 있는 "FirstPersonCharacter" 는 코딩문맥에서 보면 패키지인가요 애셋인가요?

 

 

답은 애셋입니다. 좀 더 명확하게 하기 위해서 익스플로러에서 이 파일을 복제해 보겠습니다.

 

 

보이시나요? 동일한 "*.uasset" 파일을 복사하게 되면 파일의 이름은 다른데 칸텐츠 브라우저에서의 이름은 동일합니다. 그러므로 제가 위에서 "*.uasset" 은 패키지이고 칸텐츠 브라우저에서는 애셋으로 표현된다고 말씀드린 것입니다. 절대 "FirstPersonCharacter2" 라는 이름으로 애셋이름이 변경되지 않습니다.

 

"에이~ 내가 해봤는데 칸텐츠 브라우저에서 애셋 복사하면 이름 바뀌던데?" 라고 반문하시는 분 있을 겁니다. 맞습니다. 칸텐츠 브라우저에서 애셋을 복사하면 패키지 복사와 함께 이름 변경까지 수행해 줍니다. 그러나 이는 칸텐츠 브라우저의 동작이지 패키지의 동작은 아니라는 겁니다. 그러므로 나중에 패키지를 코드를 통해 복사하게 되면 문제가 발생하는 겁니다.

 

정리하자면 패키지라는 것은 "*.uasset" 을 이야기하는 것이고 애셋이라는 것은 "*.uasset" 에 포함되어 있는 대표 오브젝트를 의미합니다. 편의상 이를 애셋이라는 이름으로 개념화한거죠. 여러분이 코드를 보실 때 주의하실 점은 "class UAsset" 과 같은 검색어를 날릴 필요는 없다는 것입니다. 존재하지 않습니다. UObject 를 상속한 어떠한 오브젝트라도 패키지의 대표 오브젝트가 될 수 있습니다.

 

3. Paths

 

이제 패키지와 애셋이 다르다는 것을 인지했으니 코드 상에서는 이에 어떻게 접근을 하는지 알아 보도록 하겠습니다. 위에서 한 것과 똑같은 동작을 코드에서 수행해 보도록 하겠습니다.

 

3.1. LongPackageName

 

일단 패키지를 로드하기 위해서는 어떤 경로를 사용해야 할까요? 여러 분이 칸텐츠 브라우저에서 애셋 위에 마우스를 올리시면 다음과 같은 툴팁을 보실 수 있습니다.

 

 

저기에서 "/Game" 이라는 이름으로 "Path" 가 시작되는 것이 보이실 겁니다. UE4 에서 사용하는 모든 칸텐츠의 경로는 저 "/Game" 이라는 접두어로 시작됩니다. 그것은 "/Content" 라는 이름에 대한 키처럼 사용됩니다. 아래 그림을 보시면 붉은색 박스가 게임 디렉토리이고 녹색 박스가 위에서 표현하고 있는 Path 입니다. 단지 "Content" 라는 이름이 "Game" 이라는 이름으로 변경되었을 뿐이죠. 

 

 

 

어쨌든 이 경로와 확장자를 제거한 패지키 이름을 합치면 그것을 LongPackageName 이라 부릅니다. 이 LongPackageName 에서는 "\" 가 아니라 "/" 를 디렉토리 분리자로 사용합니다.

 

 

우리의 경우에는 "/Game/FirstPersonCPP/Blueprints/FirstPersonCharacter" 가 되겠죠. 

 

 

3.2. Filename

 

앞에서 로드한 패키지를 "/Game/FirstPersonCPP/Blueprints/FirstPersonCharacter2" 이라는 이름으로 저장해 보도록 하겠습니다. 그런데 패키지를 저장하는 메서드는 Filename 이라는 인자를 요구하는군요.

 

 

Filename 이라는 인자를 요구하는 경우에는 절대 경로를 요구하는 것이라 보시면 됩니다. 확장자까지 확실히 붙어 있어야 합니다. 우리는 LongPackaeName 을 알고 있기 때문에 이를 절대 경로로 바꾸는 과정을 거치게 됩니다. 아래 코드를 살펴 보십시오. 어렵지 않을 것입니다.

 

 

FPackagePath::LongPackageNameToFilename() 은 LongPackageName 을 상대 경로인 파일경로로 바꿔줍니다. DestRelFilename 은 "../../../../Users/lifeisforu/Documents/Unreal Projects/PackageTest/Content/FirstPersonCPP/Blueprints/FirstPersonCharacter2.uasset" 이 나왔습니다. 이 메서드의 두 번째 인자로 공급해 주면 DestLongPackageName 에 확장자를 포함시켜주지 않아도 되고 임시 변수를 만들지 않아도 됩니다.

 

FPaths::ConvertRelativePathToFull() 은 상대경로를 절대경로로 바꿔줍니다. 이 시점에서 DestAbsFilename 은 "C:/Users/lifeisforu/Documents/Unreal Projects/PackageTest/Content/FirstPersonCPP/Blueprints/FirstPersonCharacter2.uasset" 이 나옵니다.

 

하지만 이것은 Windows 플랫폼에 맞는 이름이 아니죠. 그래서 플랫폼에 맞게 분리자 문자( '/' )를 교정해 줍니다. FPaths::MakePlaformFilename() 이 그 역할을 합니다. 그러면 DestAbsFilename 은 "C:\Users\lifeisforu\Documents\Unreal Projects\PackageTest\Content\FirstPersonCPP\Blueprints\FirstPersonCharacter2.uasset" 이 됩니다.

 

이제 패키지를 저장하고 나서 보면... 짠!

 

 

위에서 나온 것과 같은 상황이 발생합니다. 애셋 이름과 패키지 이름이 다릅니다. 그냥 패키지만 복사하면 이게 정상입니다.

 

3.3. ObjectPath

 

이제 애셋의 이름을 정상적으로 고쳐줄 필요가 있겠군요. 그러나 package 관련 메서드들에는 절대 RenameAsset 과 관련한 이름이 존재하지 않습니다. 정말 우울한 일이죠. 애셋을 로드해야만 합니다.

 

여기에서 ObjectPath 라는 개념이 나옵니다. StaticLoadObject 는 "Name" 이라는 인자를 받습니다. "LongPackageName" 이나 "Filename" 이 아닌 그냥 "Name" 이라는 인자는, 그것이 오브젝트와 관련되어 있다면, 십중팔구 ObjectPath 라 생각하시면 됩니다.

 

ObjectPath 라는 것은 "{LongPackageName}.{AssetName}" 입니다. 애셋의 이름이 마치 확장자인 것처럼 사용됩니다. 그러나 프로그래머 관점에서는 클래스의 필드 이름 정도로 생각하시는 것이 좋을 것입니다. A 오브젝트의 B 필드에는 A.B 라는 식으로 접근하니 이해하기 편할 것입니다.

 

패키지 파일은 복사했지만 애셋 이름은 고친적이 없으니 이 애셋을 로드하기 위해서는 그 경로는 "/Game/FirstPersonCPP/Blueprints/FirstPersonCharacter2.FirstPersonCharacter" 입니다. 이를 "/Game/FirstPersonCPP/Blueprints/FirstPersonCharacter2.FirstPersonCharacter2" 로 고쳐야겠죠.

 

 

이제 애셋이름이 패키지 이름과 동일하게 생성되는 것을 확인할 수 있습니다.

 

 

4. 결론

 

정리하자면 다음과 같습니다.

 

    • 패키지 : uasset 파일.
    • 애셋 : 패키지를 대표하는 오브젝트.
    • LongPackageName : "/Game" 으로 시작하는 확장자를 배제한 패키지 이름.
    • Filename : 시스템에서의 절대 경로. FPackageName 과 FPaths 라는 유틸리티를 사용해서 LongPackageName 으로부터 변환할 수 있음.
    • ObjectPath : 패키지 이름과 애셋이름을 합친 경로. 애셋이름이 확장자처럼 사용됨.
    • 코드에서 패키지를 복사하려면 애셋 이름도 반드시 따로 변경해야 함.

 

+ Recent posts