Addressables 의 원격 배포는 카탈로그가 이끈다
라이브 게임에서 아이템 이미지 하나를 고쳤다고 앱을 다시 내고 심사를 기다릴 수는 없습니다. 번들을 서버에 올려 두고 바뀐 것만 받게 하고 싶은데, 그러려면 게임이 “무엇이 바뀌었는지” 를 알아야 합니다.
Addressables 에서 그 일을 하는 것이 카탈로그입니다. 주소마다 어느 번들의 어느 에셋인지와 번들의 해시가 적힌 표라서, 카탈로그를 새것으로 바꾸면 바뀐 번들만 새 이름으로 가리키게 됩니다. 원격 배포는 결국 카탈로그를 바꿔 끼우는 일입니다.
원격 그룹은 카탈로그와 번들을 서버에 둔다
그룹의 빌드 경로와 로드 경로를 원격으로 바꾸고, 설정에서 원격 카탈로그를 켜면 빌드 결과가 서버에 올릴 폴더로 나옵니다. 번들 파일, 그리고 catalog_<버전>.bin 과 그 해시를 담은 catalog_<버전>.hash 입니다.
이 측정에서는 텍스처 둘(Item0, Item1)을 원격 그룹에 Pack Separately 로 넣고, 로드 경로를 로컬 HTTP 서버(http://127.0.0.1:8765/ServerData/[BuildTarget])로 잡았습니다. 첫 실행에서 캐시를 비우고 두 번들을 받았습니다.
그다음 Item1 이미지만 바꾸고 콘텐츠 업데이트 빌드(Update a Previous Build)를 했습니다. 이 빌드는 처음 빌드 때 남긴 addressables_content_state.bin 과 비교해 바뀐 에셋이 든 번들만 새로 만들고, 새 카탈로그를 씁니다. Player 는 다시 빌드하지 않았습니다.
초기화 때 해시를 보고 새 카탈로그를 받는다
업데이트 뒤 같은 Player 를 다시 실행했을 때 서버에 들어온 요청 순서입니다.
| 첫 실행 (캐시 비움) | 업데이트 뒤 | |
|---|---|---|
| Item0 받을 크기 | 189,120 B | 0 B |
| Item1 받을 크기 | 189,303 B | 189,734 B |
| 받은 뒤 디스크 캐시 증가 | 378,469 B | 189,757 B |
| 서버가 받은 번들 요청 | item0, item1 | 새 item1 하나 |
받을 크기는 카탈로그에 적힌 번들 크기에서 나오는 값이라, 실제로 그만큼만 받았는지는 따로 봤습니다. 디스크 캐시는 새 번들 크기(189,734 B)보다 23 B 많게 늘었고, 서버 로그에도 바뀐 번들 요청 하나만 있었습니다. 바뀐 번들만 받았습니다.
그런데 업데이트 뒤 실행에서 CheckForCatalogUpdates 를 불렀더니 빈 목록이 왔습니다. 새 카탈로그는 이미 초기화 때 받았기 때문입니다. 설정의 시작 시 카탈로그 갱신 끄기(DisableCatalogUpdateOnStart)가 기본값인 꺼짐이면, InitializeAsync 가 원격 해시를 비교해 다르면 새 카탈로그를 받습니다.
yield return Addressables.InitializeAsync(); // 여기서 새 카탈로그를 이미 받는다
var check = Addressables.CheckForCatalogUpdates(false);
yield return check;
Debug.Log(check.Result.Count); // 0
CheckForCatalogUpdates 와 UpdateCatalogs 를 직접 부르는 흐름은 이 설정을 켰을 때의 것입니다. 게임 도중 업데이트 안내를 띄우려면 시작 시 갱신을 끄고 원하는 시점에 직접 확인합니다. 그 조건은 이 측정에서 재지 않았습니다.
받기 전에 크기를 알 수 있다
카탈로그가 번들 크기와 해시를 알고 있으니, 받기 전에 얼마나 받을지 알 수 있습니다. 라벨로 묶어 한 번에 물으면 그 라벨이 기대는 번들 중 캐시에 없는 것의 합을 줍니다. 이 측정에서는 라벨 AD7 로 물었을 때 업데이트 뒤 189,734 B 가 나왔습니다.
var size = Addressables.GetDownloadSizeAsync("AD7");
yield return size;
if (size.Result > 0)
{
// "업데이트 189 KB 를 받습니다" 같은 안내를 띄운 뒤 받는다
var download = Addressables.DownloadDependenciesAsync("AD7");
yield return download;
Addressables.Release(download);
}
Addressables.Release(size);
받은 번들은 디스크 캐시에 남아서, 다음 실행에서 받을 크기가 0 이 됩니다.
직접 확인하는 테스트 코드
로컬 HTTP 서버(python -m http.server 8765 --bind 127.0.0.1 --directory Builds)를 띄우고, 업데이트 전과 후에 같은 Player 를 실행합니다. 받기 전후의 디스크 캐시 크기를 같이 찍어 카탈로그 값과 따로 확인합니다.
static long CacheBytes()
{
string path = Caching.currentCacheForWriting.path;
return Directory.Exists(path)
? Directory.GetFiles(path, "*", SearchOption.AllDirectories).Sum(f => new FileInfo(f).Length)
: 0;
}
IEnumerator CheckUpdate()
{
yield return Addressables.InitializeAsync();
foreach (string key in new[] { "AD7/Item0", "AD7/Item1" })
{
var size = Addressables.GetDownloadSizeAsync(key);
yield return size;
Debug.Log($"{key}: {size.Result} B");
Addressables.Release(size);
}
long before = CacheBytes();
var download = Addressables.DownloadDependenciesAsync("AD7");
yield return download;
Debug.Log($"캐시 증가: {CacheBytes() - before} B");
Addressables.Release(download);
}
업데이트 뒤 실행에서 Item0 이 0 B, 캐시 증가가 Item1 의 새 번들 크기와 거의 같고, 서버 로그에 그 번들 하나만 찍히면 같은 결과입니다.
주의할 점
1. 콘텐츠 상태 파일을 첫 빌드와 함께 보관한다
안 지키면 생기는 문제. 콘텐츠 업데이트 빌드는 출시한 빌드가 남긴 addressables_content_state.bin 과 비교해 바뀐 것을 고릅니다. 이 파일을 잃거나 다른 빌드의 것을 쓰면, 이미 설치된 Player 를 기준으로 무엇이 바뀌었는지 고를 수 없습니다.
이렇게 쓴다. 출시한 빌드마다 그 상태 파일을 버전 관리에 함께 남기고, 업데이트 빌드는 그 파일을 지정해서 돌립니다.
2. 시작 시 카탈로그 갱신이 켜져 있다는 것을 알고 쓴다
안 지키면 생기는 문제. 기본 설정에서는 초기화가 새 카탈로그를 받아 버려서, 그 뒤의 CheckForCatalogUpdates 는 늘 빈 목록입니다. 업데이트 안내 화면을 그 결과로 띄우면 한 번도 안 뜹니다.
이렇게 쓴다. 초기화 때 받아도 되면 그대로 두고, 업데이트 시점을 직접 정하려면 시작 시 갱신을 끄고 CheckForCatalogUpdates → UpdateCatalogs 를 원하는 자리에서 부릅니다.
3. 받기 전에 GetDownloadSizeAsync 로 크기를 확인한다
안 지키면 생기는 문제. 로드할 때 번들을 받으면 사용자는 언제 얼마나 받는지 모르고, 모바일 데이터로 큰 번들을 받게 됩니다.
이렇게 쓴다. 로딩 화면에서 라벨로 받을 크기를 묻고, 0 보다 크면 안내한 뒤 DownloadDependenciesAsync 로 미리 받습니다.
정리
Addressables 의 원격 배포는 카탈로그를 바꿔 끼워, 바뀐 번들만 새 이름으로 가리키게 하는 일입니다.
- 콘텐츠 업데이트 빌드는 바뀐 에셋의 번들만 새로 만들고, Player 는 새 카탈로그를 받아 그 번들만 받았습니다(189,734 B).
- 기본 설정에서는
InitializeAsync가 원격 해시를 비교해 새 카탈로그를 받아서, 그 뒤CheckForCatalogUpdates는 빈 목록입니다. - 받기 전에
GetDownloadSizeAsync로 크기를 알 수 있고, 받은 번들은 디스크 캐시에 남아 다음에는 0 이 됩니다.