| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- booksr.co.kr
- HANBIT Academy
- C#
- 박기현
- 잡생각 정리글
- BOJ
- 이득우의 게임수학
- 김진홍 옮김
- 전공자를 위한 C언어 프로그래밍
- 생능출판
- hanbit.co.kr
- 입출력과 사칙연산
- 일기
- 백준
- 메타버스
- C++
- 주우석
- JavaScript
- The Elements of Computing Systems 2/E
- https://insightbook.co.kr/
- C
- 밑바닥부터 만드는 컴퓨팅 시스템 2판
- unity6
- 이득우
- (주)책만
- Noam Nisan
- 알고리즘
- Shimon Schocken
- 데이터 통신과 컴퓨터 네트워크
- 게임 수학
- Today
- Total
cyphen156
C# 어드레서블 카탈로그 따라하기 – 어드레서블에 ContentManifest 끼얹기 본문
요새 스컬 프로젝트를 만들고 있데, 벌써 2주일째 머리싸매고 있는 기능인 콘텐츠 동적 배칭 시스템에 대한 고민글이다.
이중 가장 처음 들어가야하는 핵심 로직 중 하나인 매니페스트 검증 로직에 대해 정리하고자 한다.
대충 이게 왜 필요해 졌냐면
월드 내의 모든 오브젝트 까지는 아니지만 정적 배치되는 맵을 제외한 거의 대부분의 오브젝트들을 내가 만드는 도메인 체계 안에 포함시키고 싶었고, 이를 통해 클라이언트 측의 리빌드 & 배포 과정 없이 서버에서의 카탈로그 & 매니페스트 수정을 통해 콘텐츠를 동적으로 수정하여 적용시키고 싶었기 때문이다.
즉, 콘텐츠 변경의 책임을 클라이언트 빌드가 아니라 서버의 데이터 상태로 옮기고자 한 것이다.
장점과 단점
이 접근 방식의 장단점은 비교적 분명하다.
장점
우선 기본 콘텐츠를 제외하면,
대부분의 오브젝트가 게임 내 필수 요소에서 제외되기 때문에
초기 부트 시 메모리 요구량이 현저히 줄어든다.
또한 동적 배칭을 전제로 하기 때문에, 도메인 체계를 사용하는 대부분의 요소들을 오브젝트 풀링 대상으로 정의할 수 있게 된다.
그 결과 DOTS까지는 아니더라도, 같은 오브젝트 군에 대해 어느 정도 시스템화된 일괄 처리가 가능해진다.
단점
반면 단점도 명확하다.
리소스 관리 체계를 구성하는 난이도가 급격히 높아진다는 점이다.
다만 이 단점은
체계만 제대로 잡아 놓는다면 오히려 장점으로 승화될 수 있는 부분이기도 하다.
또 하나의 단점은,
동적 배칭 시스템을 사용하기 때문에 필연적으로 GC가 발생한다는 점이다.
하지만 이 부분은 씬 전환이나 UI 연출 구간 등으로 발생 시점을 제한할 수 있다.
오히려 이렇게 통제된 GC 발생을 의도적으로 유도함으로써,
플레이 중 더 안정적인 프레임 레이트 유지를 기대할 수 있을 것이라고 보고 있다.
그리고 가장 큰 단점은
콘텐츠에 대한 무결성 보장을 일정 부분 포기해야 할수도 있다는 것이다.
이 부분은 모딩이나 스킨 시스템이라는 관점에서 보면 오히려 장점으로 작용할 수도 있다.
외부에서 콘텐츠를 교체하거나 확장할 수 있다는 점은 시스템적으로 꽤 매력적인 요소이기 때문이다.
하지만 공정성이 중요한 게임의 경우, 콘텐츠가 빌드 타임에 종속되지 않고 외부 요소의 영향을 받을 수 있다는 점은 분명한 리스크가 된다.
따라서 이러한 구조를 채택할 경우, 매 게임 부팅 시 전체 콘텐츠에 대한 전수 무결성 검사가 아니라 하더라도, 실제로 플레이에 사용되는 콘텐츠만큼은 별도의 무결성 검증 로직을 적용하는 등의 보조 장치가 필요해질 수 있다.
매니페스트 검증 로직의 전체 흐름
매니페스트 검증 및 로딩 흐름은 다음과 같다.
[게임 시작]
↓
[1] Resources에서 기본 Manifest 로드
→ 항상 성공 (빌드에 포함)
↓
[2] 서버에서 meta 다운로드 시도
↓
성공?
├─ YES → 서버 meta와 로컬 비교
│ ↓
│ 다름?
│ ├─ YES → 서버에서 새 Manifest 다운
│ │ → PersistentDataPath에 저장
│ │ → 이것을 사용 (최신)
│ │
│ └─ NO → PersistentDataPath 있음?
│ ├─ YES → PersistentDataPath 사용 (캐시된 최신)
│ └─ NO → Resources 사용 (기본)
│
└─ NO (오프라인/서버 장애)
↓
PersistentDataPath에 Manifest 있음?
├─ YES → 이것을 사용 (마지막 온라인 상태)
└─ NO → Resources 사용 (기본, 오프라인 플레이 가능)
이 로직은 근본적으로 보면
Unity Addressables의 라벨 시스템과 원격 카탈로그 업데이트 시스템과 동일한 문제를 해결하고 있다.
다만 차이점이 있다면, Addressables 내부에 암묵적으로 포함되어 있던 검증·판단·갱신 과정을
ContentManifest와 meta 파일로 분리하여 명시적으로 제어하고 있다는 점이다.
우선 내가 정의한 Manifest.json은 다음과 같다.
{
"version": 1,
"verifyRoot": "http://127.0.0.1:5001",
"metaApi": "api/meta?id={Id}&schema={Schema}",
"id" : "ContentManifest",
"schema": "Manifest",
"catalogs": [
{
"id": "COMMON",
"schema": "Catalog",
"requiredOnBoot": true
},
{
"id": "STAGE0",
"schema": "Catalog",
"requiredOnBoot": false
}
]
}
이 파일은 총 세 위치에 존재할 수 있다.
- Unity Project의 Resources 폴더 내부 (필수 포함 패키지)
- Application.persistentDataPath 내부
- 서버 측 ApiServer / 인증 디렉터리 내부
이 Manifest 파일은 실질적인 콘텐츠 데이터 자체라기보다는,
어떤 콘텐츠가 존재하는지를 정의하는 일종의 DLC 체크리스트에 해당한다.
여기에는 씬 목록을 배치할 수도 있고, 나처럼 애셋 번들 팩 단위의 콘텐츠 정의를 배치할 수도 있다.
그리고 이 Manifest.json은 SHA-256 해시 함수를 통해 경량화된 데이터로 요약되어 meta 파일을 구성하게 된다
Meta 파일의 역할
meta파일은 클라이언트가 서버측에 있는 meta파일과 비교하여 현재 내가 가지고 있는 manifest 파일이 최신버전인지를 검증하는 일종의 체크섬 역할을 하게 된다.
이 검증 결과에 따라, 앞서 정리한 로딩 흐름에서처럼 Manifest를 다시 다운로드할지, 기존 데이터를 그대로 사용할지가 결정된다.
Manifest 검증 이후의 전개
Manifest의 최신 여부가 확정되면, 클라이언트는 Manifest 본문에 정의된 내용을 기반으로 어떤 콘텐츠를 로드할 것인지를 판단하게 된다. 여기서 핵심이 되는 것이 Catalog이다.
Catalog는
실제 애셋 번들 및 개별 콘텐츠에 대한 인덱스 역할을 하는 기준점으로 동작하며, 이 특성상 Manifest와 동일하게 meta 검증 과정을 거친 뒤 CDN/Nginx를 통해 최신 상태로 유지된다.
즉, Manifest → Catalog → AssetBundle로 이어지는 계층 구조 전반이 동일한 Meta 기반 검증 규칙을 공유한다.
콘텐츠 다운로드
Catalog 본문에는 실제 로드 대상이 되는 애셋 번들 정보가 포함되며, 이 특성상 파일 용량이 커질 가능성이 높다.
따라서 Catalog 및 그 하위 콘텐츠들은 정적 파일 형태로 비동기 다운로드되며, 이 과정은 상황에 따라 다음과 같이 운용될 수 있다.
- 플레이 중에도 백그라운드에서 지속적으로 다운로드하거나
- 모든 필수 콘텐츠가 최신화된 이후에만 플레이를 허용하거나
이러한 구조를 통해 플레이 흐름에 맞춘 On Playing Download 로직을 구성할 수 있으며, 콘텐츠 배칭 단위 역시
Manifest와 Catalog에 정의된 기준에 따라 유연하게 제어할 수 있게 된다.
런타임 콘텐츠 동적 배칭
이 부분은 실제로 도메인 시스템 기반으로 애셋 번들이 빌드된 이후에 보다 구체적으로 설명할 수 있는 영역이긴 하다.
다만 전체 구조를 이해하는 데 도움이 될 정도로, 여기서는 개념 수준에서만 간단히 정리해 보려고 한다.
스테이지 프리팹 구성
우선 아래와 같이 2개의 스테이지 프리팹이 있다.


이 프리팹들은 챕터 진행에 따라 플레이어에게 제공되는 정적 맵에 해당하는 스테이지 프리팹이다.
일반적인 경우 하나의 씬 안에 여러 오브젝트들을 플레이어를 포함해서 다양하게 배치하여 씬 자체를 빌드하겠지만,
나는 씬을 텅 빈 깡통으로 사용하고 실제 스테이지 구성은 런타임에 동적으로 배치한다.
그리고 이 씬 내부에는 다음과 같이 SpawnPoints 가 존재한다.
이 SpawnPoint들은 실제 오브젝트가 배치될 위치 정보만을 담당하며, 콘텐츠 자체는 포함하지 않는다.
에디터 단계에서 추출되는 데이터
레벨 디자이너가 에디터 상에서 SpawnPoint에 원하는 프리팹을 배치하면, 다음 정보들을 추출하게 된다.
- 배치된 프리팹의 도메인 정보
- 해당 오브젝트의 Transform 정보
- 어떤 위치에 어떤 오브젝트가 배치되는지에 대한 배치 데이터
이렇게 추출된 데이터는 씬에 종속되지 않은 순수 데이터 형태로 저장되며, 이후 StageManager라는 배칭 관리 컨텍스트에 의해 제어된다.
서버 데이터 변경만으로 콘텐츠 교체
2D Square 스프라이트가 배치되어 있다고 가정하자.

이 상태에서 서버 측의 데이터만 수정하면, 클라이언트는 별도의 빌드 없이도 특정 위치에 2D Red Triangle 프리팹을 즉시 배치할 수 있다.
전제 조건은 단 하나다.
해당 프리팹이 리소스 매니저에 의해 관리되는 도메인 체계 안에 존재할 것.
이 경우 클라이언트가 새로 내려받아야 하는 데이터는
- 변경된 Stage0_0 배치 데이터 애셋
- 새로 필요한 2D Red Triangle 프리팹 애셋 번들
총 두 개의 파일뿐이다.

기존 방식과의 차이
일반적인 씬 기반 구조라면,
- 스테이지가 포함된 씬 전체
- 해당 씬에 종속된 모든 애셋 번들
을 다시 다운로드하여 최신화해야 했을 것이다.
하지만 이 방식에서는
- 변경된 배치 데이터
- 실제로 추가된 프리팹
만을 선택적으로 다운로드하면 되며,
이는 곧 런타임 콘텐츠 동적 배칭의 핵심적인 이점으로 이어진다.
이 구조를 통해
콘텐츠 로딩은 더 이상 “씬 단위”가 아니라
도메인과 데이터 단위의 배칭 문제로 전환된다.
그리고 이 지점에서 앞서 정리한 Manifest / Catalog / Meta 기반 구조가
비로소 실제 런타임 로직으로 연결되기 시작한다.
사실 이게 딱히 엄청 혁신적이거나 한 그런 방법은 아니다
이미 유니티 내부에서 사용되는 meta파일을 조금 내 입맛대로 조정했을 뿐이다.
다음 글을 쓸지 말지는 고민을 좀 해봐야겟다.
'프로그래밍 > C#' 카테고리의 다른 글
| C# Array of Generic Dictionary에 대한 고찰 (0) | 2026.01.27 |
|---|---|
| 파일 정합성 인증 - 무결성 검사 정책 정하기 (0) | 2026.01.10 |
| C# 병렬성과 비동기 Async와 Await에 대한 고찰 (0) | 2025.10.09 |
| C# 문자열 변수 할당 최적화 (0) | 2025.04.01 |
| C# 반복자 패턴 IEnumerator과 yield키워드 (0) | 2025.02.25 |
