Addressables 는 AssetBundle 위에 주소를 얹는다
AssetBundle 을 직접 쓰면 일이 계속 늘어납니다. 어느 에셋이 어느 번들에 있는지 기억해야 하고, 의존 번들을 순서대로 열어야 하고, 누가 아직 쓰는지 세다가 내려야 하고, 서버에 올린 번들의 경로와 버전도 관리해야 합니다.
Addressables 는 에셋에 주소를 붙이고, 그 주소로 로드하면 나머지를 대신 해 주는 패키지입니다. 밑에서는 여전히 AssetBundle 이 돌고, Addressables 는 그 위에 “주소 → 위치 → 번들” 을 찾아 주는 층을 얹은 것입니다. 매뉴얼도 AssetBundle 을 에디터에서 정의하고 빌드하는 쉬운 방법이라며 저수준 API 대신 이것을 권합니다.
주소로 부르면 번들은 Addressables 가 연다
에셋을 Addressable 로 표시하면 주소가 붙습니다. 기본값은 프로젝트 경로이고 원하는 이름으로 바꿀 수 있습니다. 라벨을 붙이면 여러 에셋을 한꺼번에 부를 수도 있습니다. 코드는 주소만 압니다.
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
yield return handle;
GameObject instance = Instantiate(handle.Result);
// 다 쓰면 로드한 횟수만큼 놓는다
Destroy(instance);
Addressables.Release(handle);
이 한 줄 뒤에서 일어나는 일입니다.
카탈로그는 빌드할 때 함께 나오는 표입니다. 주소마다 어느 번들의 어느 에셋인지, 그 번들이 어느 번들에 기대는지, 번들 파일이 로컬 경로에 있는지 서버 URL 에 있는지가 들어 있습니다. 그래서 같은 주소가 개발 중에는 로컬 번들을, 출시 뒤에는 서버의 번들을 가리킬 수 있습니다.
번들 구성은 그룹으로 정합니다. 에셋을 그룹에 넣으면 빌드가 그룹 설정대로 번들을 만듭니다. 번들 이름을 에셋마다 적던 일이 그룹 단위 설정으로 바뀐 것입니다. AssetReference 필드를 쓰면 문자열 대신 인스펙터에서 고른 에셋으로 참조할 수 있어서 주소 오타를 컴파일 단계 가까이에서 막을 수 있습니다.
에디터 기본 모드는 그룹 설정을 보지 않는다
편해진 대가로 함정이 하나 생깁니다. 에디터의 Play Mode Script 기본값인 Use Asset Database 는 번들을 만들지 않고 프로젝트의 에셋을 바로 읽습니다. 빌드 없이 바로 돌릴 수 있어 빠른 대신, 빌드된 번들과 다르게 동작할 수 있습니다.
그룹 하나의 Include in Build 를 끄고, 그 그룹의 에셋을 세 환경에서 로드했습니다.
| 실행 | 빌드에 넣은 그룹 | Include in Build 를 끈 그룹 |
|---|---|---|
| 에디터, Use Asset Database | 성공 | 성공 |
| 에디터, Use Existing Build | 성공 | InvalidKeyException |
| Player | 성공 | InvalidKeyException |
에디터에서는 되는 로드가 Player 에서는 No Location found for Key=… 로 실패했습니다. Addressables 4.1.1 소스를 보면 이유가 나옵니다. Use Asset Database 모드는 Editor/Build/FastModeInitializationOperation.cs 에서 AddressableAssetSettingsLocator 를 등록하는데, 이 로케이터는 모든 그룹의 모든 엔트리를 키 표에 넣고 IncludeInBuild 를 확인하지 않습니다(Editor/Build/AddressableAssetSettingsLocator.cs).
Use Existing Build 는 Player 와 같은 결과를 냈습니다. 실제로 빌드한 카탈로그와 번들을 읽기 때문입니다.
콘텐츠 빌드를 아예 빠뜨리면 Player 는 초기화부터 실패합니다. 이 프로젝트는 Player 빌드 때 Addressables 를 같이 빌드하지 않게 설정했는데, 콘텐츠를 빌드하지 않고 Player 만 만들면 RuntimeData is null. Please ensure you have built the correct Player Content. 이 찍히고 모든 키가 실패했습니다.
직접 확인하는 테스트 코드
Include in Build 를 끈 그룹의 에셋을 Play Mode 테스트와 Player 에서 같은 코드로 로드합니다. Play Mode Script 를 바꿔 가며 테스트를 돌리고, 같은 코드를 Player 에서도 실행합니다.
public class AddressablesKeyTest
{
[UnityTest]
public IEnumerator ExcludedGroupIsNotInBuild()
{
yield return Addressables.InitializeAsync();
foreach (string key in new[] { "AD1/Included", "AD1/Excluded" })
{
var handle = Addressables.LoadAssetAsync<Texture2D>(key);
yield return handle;
Debug.Log($"{key}: {handle.Status} {handle.OperationException?.Message}");
if (handle.IsValid()) Addressables.Release(handle);
}
}
}
Use Asset Database 에서는 둘 다 Succeeded, Use Existing Build 와 Player 에서는 AD1/Excluded 만 Failed 와 InvalidKeyException 이 나오면 같은 결과입니다.
주의할 점
1. 출시 전 확인은 Use Existing Build 로 한다
안 지키면 생기는 문제. Use Asset Database 는 그룹 설정을 무시하고 프로젝트 에셋을 바로 읽어서, 빌드에서 빠진 에셋도 에디터에서는 로드됩니다. 에디터 테스트를 다 통과한 기능이 Player 에서 키를 못 찾습니다.
이렇게 쓴다. 개발 중에는 Use Asset Database 로 빠르게 돌리고, 확인 단계에서는 콘텐츠를 빌드한 뒤 Use Existing Build 로 돌립니다.
2. Player 빌드에 콘텐츠 빌드를 묶어 둔다
안 지키면 생기는 문제. 콘텐츠를 빌드하지 않거나 옛 콘텐츠로 Player 를 만들면 초기화가 실패하거나 새 키를 못 찾습니다. 빌드 자체는 성공합니다.
이렇게 쓴다. Addressables 설정의 Player 빌드 때 콘텐츠도 빌드하는 옵션을 켜 두거나, 빌드 스크립트에서 AddressableAssetSettings.BuildPlayerContent() 를 Player 빌드 앞에 부릅니다.
3. 주소 문자열은 한 곳에서만 쓴다
안 지키면 생기는 문제. 주소는 문자열이라 오타나 주소 변경이 컴파일에서 안 잡히고, 런타임에 InvalidKeyException 으로만 드러납니다.
이렇게 쓴다. 인스펙터에서 연결하는 AssetReference 필드를 쓰거나, 주소를 상수로 모아 둔 클래스 하나에서만 가져다 씁니다.
정리
Addressables 는 AssetBundle 위에 주소와 카탈로그를 얹어, 위치 찾기와 의존 번들 열기를 대신합니다.
- 코드는 주소로
LoadAssetAsync하고Release로 짝을 맞추면 됩니다. 어느 번들인지, 무엇을 먼저 열지는 카탈로그가 압니다. - 번들 구성은 에셋마다 번들 이름을 적는 대신 그룹 설정으로 정합니다.
- 에디터 기본 모드 Use Asset Database 는 Include in Build 를 보지 않아서, 에디터에서만 되는 로드가 생깁니다. 출시 전에는 Use Existing Build 로 확인합니다.