
Addressables Assets로 모듈식 DLC와 패치 시스템 구성하기
Unity Addressables의 카탈로그와 원격 번들 구조를 이용해 DLC를 독립 배포하고 앱 재설치 없이 콘텐츠 패치를 적용하는 방법을 정리합니다. 빌드 파이프라인과 런타임 로딩, 호환성 검증까지 실무 관점에서 다룹니다.
목표와 전제
Addressables는 에셋의 실제 위치를 코드에서 분리하는 Unity 패키지다. 로컬 번들과 원격 번들을 같은 방식으로 다룰 수 있으며 카탈로그를 통해 키와 에셋 위치를 연결한다. 이 구조를 활용하면 실행 파일을 다시 배포하지 않고도 데이터 중심 콘텐츠를 추가하거나 교체할 수 있다.
이 글에서 말하는 DLC는 맵, 캐릭터 프리팹, 대사, 아이콘처럼 Addressables로 관리 가능한 콘텐츠 묶음이다. 네이티브 플러그인, C# 코드, 셰이더 런타임 자체를 교체하는 패치에는 적합하지 않다. 그런 변경은 일반적으로 앱 업데이트가 필요하다.
전체 구조
핵심은 기본 게임과 각 DLC가 별도의 카탈로그 및 번들 집합을 갖도록 만드는 것이다. 클라이언트는 먼저 기본 카탈로그를 사용하고 구매 또는 권한 확인이 끝난 DLC의 카탈로그만 추가로 불러온다.
flowchart LR
A[기본 게임 앱] --> B[기본 Addressables 카탈로그]
B --> C[기본 콘텐츠 번들]
A --> D[권한 및 구매 확인]
D --> E[DLC 카탈로그 URL]
E --> F[DLC 번들]
A --> G[원격 카탈로그 업데이트 확인]
G --> H[패치 번들 다운로드]
기본 게임은 DLC의 에셋을 직접 참조하지 않는 편이 안전하다. 직접 참조가 남아 있으면 기본 번들에 DLC 의존성이 섞이거나 DLC가 없는 환경에서 참조가 깨질 수 있다. 공통 인터페이스와 문자열 키, 레이블, ScriptableObject 기반 메타데이터를 경계로 삼으면 결합도를 낮출 수 있다.

Addressables 그룹 설계
기본 콘텐츠와 DLC를 분리한다
Addressables Groups 창에서 최소한 다음 성격의 그룹을 나눈다.
Base_Local: 첫 실행에 반드시 필요한 UI, 부트스트랩 씬, 기본 데이터Base_Remote: 앱 출시 후 교체 가능하지만 모든 사용자가 받는 콘텐츠DLC_Fantasy: 판타지 팩 전용 씬, 프리팹, 오디오, 데이터DLC_SciFi: SF 팩 전용 콘텐츠Shared_Remote: 여러 DLC가 함께 쓰는 대용량 공통 에셋
DLC 그룹에는 dlc-fantasy처럼 DLC 식별용 레이블을 부여한다. 단, 레이블만으로 배포 단위를 분리할 수는 없다. 실제 배포 경계는 그룹의 번들 빌드 설정과 별도 카탈로그 생성 방식으로 결정된다.
번들 구성 원칙
한 프리팹이 사용하는 머티리얼, 텍스처, 애니메이션이 서로 다른 그룹에 흩어지면 다운로드와 메모리 추적이 어려워진다. 콘텐츠 단위로 함께 로드되는 에셋은 가능하면 같은 DLC 그룹에 둔다.
반대로 여러 DLC가 공통으로 사용하는 에셋은 별도 공유 그룹으로 분리한다. 다만 공유 그룹을 너무 크게 만들면 작은 DLC 하나를 위해 불필요한 데이터를 내려받게 된다. 공유 여부는 실제 의존 관계와 예상 다운로드 크기를 기준으로 판단해야 한다.
DLC끼리 순환 의존하는 구조는 피한다. 예를 들어 판타지 DLC의 캐릭터가 SF DLC의 머티리얼을 참조하면 판타지 DLC 단독 설치라는 약속이 깨진다.
원격 경로와 프로필 설정
Addressables Profiles에서 빌드 결과가 저장되는 경로와 런타임 다운로드 주소를 분리한다. 개발 환경에서는 로컬 웹 서버를 쓸 수 있지만 출시 환경에서는 CDN이나 객체 스토리지를 사용하는 편이 일반적이다.
예를 들어 원격 로드 경로는 다음처럼 플랫폼과 콘텐츠 버전을 포함할 수 있다.
https://cdn.example.com/game/addressables/[BuildTarget]/[ContentVersion]/
ContentVersion을 릴리스마다 바꾸면 롤백과 캐시 관리가 쉬워진다. 다만 카탈로그 URL까지 매번 바꾸면 클라이언트가 새 위치를 알 방법이 필요하다. 운영 방식은 크게 두 가지다.
- 카탈로그의 고정 URL을 유지하고 파일 내용을 갱신한다.
- 부트스트랩 설정 파일이나 서버 API로 현재 카탈로그 URL을 내려준다.
첫 방식은 단순하지만 CDN 캐시 무효화 정책을 세심하게 관리해야 한다. 두 번째 방식은 유연하지만 부트스트랩 API의 가용성과 보안이 추가로 필요하다.
DLC 빌드와 배포 절차
DLC를 별도 카탈로그로 제공하려면 기본 콘텐츠와 DLC 콘텐츠를 같은 배포 결과물로 취급하지 않아야 한다. 프로젝트 규모와 Addressables 버전에 따라 세부 메뉴와 설정 이름은 다를 수 있지만 절차는 다음과 같다.
- 기본 게임용 Addressables 콘텐츠를 빌드하고 앱에 포함할 카탈로그를 생성한다.
- DLC 전용 그룹을 대상으로 별도 빌드를 수행해 DLC 카탈로그와 번들을 생성한다.
- DLC 번들과 카탈로그 JSON, 해시 파일을 CDN의 전용 경로에 업로드한다.
- 스토어 구매 또는 게임 서버 권한 확인을 마친 사용자에게만 DLC 카탈로그 URL을 제공한다.
- 클라이언트에서 카탈로그를 로드한 뒤 필요한 레이블의 의존성을 내려받는다.
카탈로그와 번들은 반드시 같은 배포 세트로 관리해야 한다. 새 카탈로그가 이전 번들을 가리키거나 새 번들만 먼저 올라간 상태는 로드 실패와 재현하기 어려운 캐시 문제를 만든다. 업로드는 버전 경로에 완결된 파일 세트를 먼저 배치한 뒤 마지막에 현재 버전을 가리키는 설정을 변경하는 방식이 안전하다.
런타임에서 DLC 카탈로그 로드하기
아래 예제는 권한 확인을 마친 뒤 외부 카탈로그를 추가하고 DLC 레이블의 다운로드 크기를 확인한 다음 필요한 번들을 받는 흐름이다.
using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public sealed class DlcContentService
{
public async Task<bool> InstallFantasyDlcAsync(string catalogUrl)
{
AsyncOperationHandle<IResourceLocator> catalogHandle =
Addressables.LoadContentCatalogAsync(catalogUrl, false);
IResourceLocator locator = await catalogHandle.Task;
if (locator == null)
{
Debug.LogError($"DLC 카탈로그를 불러오지 못했습니다: {catalogUrl}");
return false;
}
const string label = "dlc-fantasy";
AsyncOperationHandle<long> sizeHandle =
Addressables.GetDownloadSizeAsync(label);
long downloadSize = await sizeHandle.Task;
Addressables.Release(sizeHandle);
if (downloadSize > 0)
{
AsyncOperationHandle downloadHandle =
Addressables.DownloadDependenciesAsync(label);
await downloadHandle.Task;
if (downloadHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError("DLC 번들 다운로드에 실패했습니다.");
Addressables.Release(downloadHandle);
return false;
}
Addressables.Release(downloadHandle);
}
Addressables.Release(catalogHandle);
return true;
}
}
LoadContentCatalogAsync가 성공했다고 해서 모든 DLC 파일이 내려받아졌다는 뜻은 아니다. 이 호출은 카탈로그를 메모리에 추가한다. 실제 번들은 해당 에셋을 로드할 때 받거나 위 예제처럼 DownloadDependenciesAsync로 미리 내려받는다.
다운로드 진행률이 필요하다면 DownloadDependenciesAsync 핸들에서 GetDownloadStatus()를 주기적으로 읽어 UI에 표시한다. 네트워크 오류, 저장 공간 부족, 사용자 취소도 별도 상태로 처리해야 한다.
콘텐츠 패치 적용하기
모든 사용자가 쓰는 원격 콘텐츠는 기본 카탈로그의 업데이트 기능으로 관리할 수 있다. 앱 시작 직후가 아니라 타이틀 화면이나 콘텐츠 진입 전처럼 사용자가 기다릴 수 있는 시점에 확인하는 편이 좋다.
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public static class ContentUpdateService
{
public static async Task<bool> CheckAndApplyAsync()
{
AsyncOperationHandle<List<string>> checkHandle =
Addressables.CheckForCatalogUpdates(false);
List<string> catalogs = await checkHandle.Task;
Addressables.Release(checkHandle);
if (catalogs == null || catalogs.Count == 0)
return false;
AsyncOperationHandle<List<IResourceLocator>> updateHandle =
Addressables.UpdateCatalogs(catalogs, false);
await updateHandle.Task;
bool succeeded = updateHandle.Status == AsyncOperationStatus.Succeeded;
Addressables.Release(updateHandle);
return succeeded;
}
}
카탈로그를 갱신해도 이미 메모리에 로드한 에셋 인스턴스가 자동으로 교체되지는 않는다. 씬, 프리팹, ScriptableObject를 사용 중인 상태에서 패치를 적용했다면 다음 콘텐츠 진입 시점에 새 키를 다시 로드하도록 설계하는 편이 예측 가능하다.
코드와 데이터의 호환성 경계
Addressables 패치는 데이터 교체에는 강하지만 코드 호환성 문제를 해결하지는 않는다. 새 DLC 데이터가 기존 클라이언트에 없는 컴포넌트나 직렬화 필드를 요구하면 역직렬화 또는 런타임 동작이 실패할 수 있다.
따라서 DLC 메타데이터에는 최소 클라이언트 버전과 콘텐츠 버전을 둔다. 서버가 카탈로그 URL을 전달할 때 이 정보를 함께 주고 지원하지 않는 클라이언트에는 앱 업데이트를 유도한다.
[Serializable]
public sealed class DlcManifest
{
public string id;
public string catalogUrl;
public string requiredAppVersion;
public string contentVersion;
}
에셋 키도 공개 API처럼 다루는 것이 좋다. 이미 배포한 키를 무심코 변경하면 이전 저장 데이터, 원격 설정, 이벤트 테이블이 참조하는 콘텐츠를 찾지 못할 수 있다. 키 변경이 필요하면 이전 키를 별칭으로 유지하거나 마이그레이션 테이블을 마련한다.
캐시와 제거 전략
다운로드한 번들은 Unity 캐시에 남는다. DLC 구매 취소나 저장 공간 확보 기능을 제공할 때는 앱이 현재 사용 중인 핸들을 해제한 뒤 캐시 정리를 검토해야 한다. 하지만 공용 번들은 다른 DLC가 참조할 수 있으므로 단순히 특정 DLC 파일만 삭제한다고 가정하면 안 된다.
가장 단순한 전략은 DLC를 명확히 독립된 번들 집합으로 만들고 제거 기능은 다음 실행 시 적용하는 것이다. 실행 중인 에셋을 제거하면 씬 전환이나 재사용 과정에서 예상하지 못한 재다운로드가 발생할 수 있다.
배포 전 점검 목록
- 빈 캐시 상태에서 기본 게임만 설치해 정상 실행되는지 확인한다.
- DLC를 구매하지 않은 계정에서 DLC 키와 씬에 접근할 수 없는지 확인한다.
- DLC 카탈로그만 로드한 뒤 필요한 번들이 실제로 내려받히는지 확인한다.
- 네트워크 중단, CDN 404, 디스크 공간 부족에서 오류 메시지와 재시도 흐름을 확인한다.
- 이전 클라이언트가 새 콘텐츠를 받았을 때 호환성 검사가 동작하는지 확인한다.
- 카탈로그와 번들을 이전 버전으로 되돌렸을 때 정상적으로 롤백되는지 확인한다.
- 개발 빌드뿐 아니라 IL2CPP와 대상 플랫폼의 실제 빌드에서도 검증한다.
마무리
Addressables 기반 DLC 시스템의 핵심은 다운로드 API 자체보다 콘텐츠 경계를 명확히 하는 데 있다. 기본 게임, 공용 콘텐츠, 각 DLC의 의존성을 분리하고 카탈로그와 번들을 하나의 버전 세트로 배포하면 패치와 확장 콘텐츠를 훨씬 안정적으로 운영할 수 있다. 출시 전에는 반드시 빈 캐시, 이전 클라이언트, 실패한 다운로드처럼 정상 경로 밖의 상황을 포함해 검증해야 한다.


