---
title: "Content Directories 는 아직 로컬 전용이다"
description: "Unity 6.6 의 Content Directories 는 Player 에 넣는 로컬 콘텐츠만 다룹니다. 원격 콘텐츠를 AssetBundle 로 남기면 공유 에셋이 양쪽에 따로 빌드되고, Addressables 4.x 에서 그룹을 바꿀 때는 빌드 스크립트에 따라 경고 없이 빈 빌드가 됩니다."
author: "유니얼"
pubDate: 2026-10-19
category: "game-dev/unity/runtime"
tags: ["Unity", "메모리"]
canonical: https://blog.unialgames.com/posts/unity-content-directories-limits/
---
<x-repo>
<script type="application/json">
{
  "repo": "SeoBYP/ContentDirectoriesStudy",
  "text": "Unity 6.6 Content Directories 를 공식 문서대로 만들고, 빌드 결과와 런타임 동작을 에디터와 Player 에서 잰 기록입니다.",
  "chips": ["Unity 6000.6.3f1", "Addressables 4.1.1", "Windows Player"],
  "links": [
    { "text": "혼합 빌드 로그", "path": "measurements/cd4_addressables_mixed.log" },
    { "text": "그룹 전환 로그", "path": "measurements/cd4b_convert_runtime.log" }
  ]
}
</script>
</x-repo>

라이브 서비스 게임은 콘텐츠를 앱 업데이트 없이 서버에서 내려받습니다. Addressables 의 원격 그룹과 카탈로그가 그 일을 해 왔고, Content Directories 로 옮기려면 그 길이 있는지부터 봐야 합니다.

Unity 6.6 의 Content Directories 는 **Player 와 함께 넣는 로컬 콘텐츠만 다룹니다.** 원격 콘텐츠는 AssetBundle 로 남겨 섞어 써야 하고, 섞으면 공유하는 에셋이 양쪽에 따로 빌드됩니다. Addressables 4.x 에서 그룹을 바꾸는 길은 있지만 빌드 스크립트 하나에 따라 결과가 갈립니다.

## 6.6 에는 원격 배포가 없다

매뉴얼은 6.6 의 Content Directories 에 원격 다운로드, CDN, 카탈로그 갱신, 패치가 없다고 적습니다. Unity 포럼의 답변도 같습니다. 6.6 은 **Player 와 함께 배포하는 고정된 디렉터리**를 대상으로 하고, 동적인 콘텐츠는 이후 릴리스에서 다룬다는 것입니다.

파일 이름이 내용 해시라 바뀐 파일만 골라내기는 쉽습니다. 하지만 받아 오고, 저장하고, 옛 파일을 치우는 일을 전부 직접 만들어야 합니다. Unity 는 이것을 **Unity 7 세대**에서 넓히겠다고 예고했습니다. 기기에 없는 파일만 골라 받고, 바뀐 파일 단위로 갱신하는 방식이며 2027년에 더 공개한다고 했습니다. 구체적인 시점과 API 는 아직 없습니다.

그래서 6.6 에서 원격 콘텐츠가 필요하면 그 부분은 AssetBundle(Addressables 원격 그룹)로 남깁니다. 매뉴얼도 "원격은 AssetBundle, 로컬은 Content Directories" 로 섞어 쓰는 것을 허용합니다. 대신 경고가 하나 붙습니다.

## 섞으면 공유 에셋이 양쪽에 따로 빌드된다

Addressables 4.1.1 에서 Cube 는 Content Directory 그룹에, 같은 `Lit.mat` 을 쓰는 CubeB 는 AssetBundle 그룹에 넣고 한 번에 빌드했습니다. 같은 출력 폴더에 콘텐츠 디렉터리 아카이브와 AssetBundle 하나가 나왔고, 공유하는 에셋이 어디에 들어갔는지 양쪽 리포트를 봤습니다.

| 공유 에셋 | 콘텐츠 디렉터리 | AssetBundle |
| --- | --- | --- |
| `Lit.mat` | 들어감 | 들어감 |
| `Lit.shader` | 들어감 | 들어감 |
| `FallbackError.shader`, `FallbackShader.shader` | 들어감 | 들어감 |

매뉴얼의 경고대로 **공유 에셋이 두 번 빌드됐습니다.** 셰이더처럼 큰 공용 에셋일수록 빌드 크기가 그만큼 늘어납니다.

더 조심할 것은 이 중복이 리포트에 안 잡힌다는 점입니다. Addressables 빌드 레이아웃의 `DuplicatedAssetCount` 는 0 이었습니다. 콘텐츠 디렉터리와 AssetBundle 사이의 중복은 이 숫자로 알 수 없습니다.

콘텐츠 디렉터리끼리도 같습니다. 같은 빌드 안에서만 중복이 없고, 디렉터리를 따로 빌드하면 각자 담습니다. Unity 는 포럼 답변에서 같은 에셋이 두 디렉터리에 있으면 메모리에도 두 번 올라온다고 하고, 디렉터리를 작게 여럿 두기보다 **크게 적게** 두라고 권했습니다.

## Addressables 4.x 는 코드 그대로 옮길 수 있다

Addressables 4.x 의 Groups 창에는 그룹을 Content Directory 로 바꾸는 메뉴(Convert schema(s) to Content Directory)가 있습니다. 바꾼 그룹을 **로드 코드를 한 글자도 안 고치고** 쓸 수 있는지 Player 에서 확인했습니다. 쓴 코드는 평범한 Addressables 코드입니다.

```csharp
var handle = Addressables.LoadAssetAsync<GameObject>("Cube");
yield return handle;
var instance = Instantiate(handle.Result);
yield return null;
Destroy(instance);
Addressables.Release(handle);

var instHandle = Addressables.InstantiateAsync("Cube");
yield return instHandle;
Addressables.ReleaseInstance(instHandle.Result);
```

AssetBundle 그룹일 때와 바꾼 뒤의 결과가 같았습니다. 컴포넌트, 머티리얼, 올리고 내릴 때의 콘텐츠 머티리얼 수(2 → 3 → 2)가 전부 같고, 달라진 것은 Addressables 안에서 위치를 푸는 공급자 하나였습니다. 바꾼 뒤에는 `NativeContentAssetEntryProvider` 가 의존 번들 없이 에셋을 찾습니다. Root Asset 도 Addressables 가 빌드할 때 알아서 만들어서 직접 만들 필요가 없었습니다.

다만 이것은 **활성 빌드 스크립트가 맞을 때만** 그렇습니다. 같은 변환 그룹을 빌드 스크립트만 바꿔 빌드했습니다.

<x-compare>
<script type="application/json">
{
  "label": "변환한 그룹을 빌드한 빌더",
  "tabs": [
    { "label": "Default Build Script", "text": "변환한 그룹이 콘텐츠 디렉터리로 빌드됩니다. Player 에서 같은 코드로 Cube 가 올라옵니다.", "stats": [["빌더 클래스", "BuildScriptSchemaDriven"], ["빌드 결과", "번들 0, 콘텐츠 디렉터리 1"], ["Player", "로드 성공"]], "tone": "good" },
    { "label": "Build Script (AssetBundles)", "text": "빌드는 에러 없이 끝나는데 아무것도 안 나옵니다. Player 에서 처음 로드할 때 InvalidKeyException: No Location found for Key=Cube 가 납니다.", "stats": [["빌더 클래스", "BuildScriptPackedMode"], ["빌드 결과", "번들 0, 콘텐츠 디렉터리 0"], ["Player", "로드 실패"]], "tone": "bad" }
  ]
}
</script>
</x-compare>

AssetBundle 전용 빌더는 Content Directory 그룹을 모르니 그냥 건너뜁니다. 빌드가 성공으로 끝나서 런타임에서야 알게 됩니다. 4.1.1 에서 새로 만든 설정은 `BuildScriptSchemaDriven` 이 활성인데, 2.x 에서 올린 기존 프로젝트의 활성 빌더가 무엇이 되는지는 확인하지 못했습니다.

## 직접 확인하는 테스트 코드

혼합 빌드에서 공유 에셋이 양쪽에 들어갔는지 보려면 두 리포트를 같이 봅니다. 콘텐츠 디렉터리 쪽은 `Library/BuildHistory/<빌드ID>/ContentLayout.json` 의 `SourceAssets`, AssetBundle 쪽은 Addressables 빌드 레이아웃입니다. 활성 빌더와 결과는 에디터 스크립트로 찍을 수 있습니다.

```csharp
[MenuItem("Tools/Addressables/Build And Report")]
static void BuildAndReport()
{
    var settings = AddressableAssetSettingsDefaultObject.Settings;
    var builder = settings.ActivePlayerDataBuilder;
    Debug.Log($"활성 빌더: {builder.GetType().Name} ({builder.Name})");

    foreach (var group in settings.groups)
        Debug.Log($"{group.Name}: [{string.Join(", ", group.Schemas.Select(s => s.GetType().Name))}]");

    AddressableAssetSettings.BuildPlayerContent(out AddressablesPlayerBuildResult result);
    Debug.Log($"에러: '{result.Error}', 번들 {result.AssetBundleBuildResults.Count}개");
}
```

변환한 그룹이 있는데 활성 빌더가 `BuildScriptPackedMode` 이고 번들도 콘텐츠 디렉터리도 안 나오면 빈 빌드입니다.

## 주의할 점

### 1. 원격 콘텐츠와 로컬 콘텐츠가 같이 쓰는 에셋을 확인한다

**안 지키면 생기는 문제.** 공용 셰이더나 머티리얼이 콘텐츠 디렉터리와 AssetBundle 양쪽에 따로 들어갑니다. Addressables 리포트의 중복 수에는 안 잡혀서 빌드 크기가 왜 늘었는지 알기 어렵습니다.

**이렇게 쓴다.** 원격 그룹과 로컬 그룹이 같이 쓰는 에셋은 한쪽에만 두도록 그룹을 나누고, 빌드 뒤 `ContentLayout.json` 과 빌드 레이아웃을 같이 확인합니다.

### 2. 그룹을 바꿨으면 활성 빌더를 확인한다

**안 지키면 생기는 문제.** 활성 빌더가 AssetBundle 전용이면 변환한 그룹은 에러 없이 빌드에서 빠지고, Player 에서 `InvalidKeyException` 이 납니다.

**이렇게 쓴다.** 변환한 뒤 Build > New Build 메뉴의 Default Build Script(`BuildScriptSchemaDriven`)로 빌드하고, Play Mode Script 를 Use Existing Build 로 둔 채 한 번 돌려 봅니다.

### 3. 디렉터리를 잘게 나누지 않는다

**안 지키면 생기는 문제.** 디렉터리끼리는 중복을 못 지우니, 잘게 나눌수록 같은 에셋이 여러 디렉터리에 들어가고 메모리에도 여러 번 올라옵니다.

**이렇게 쓴다.** 디렉터리는 DLC 나 언어 팩처럼 설치 단위로 붙였다 떼야 할 때만 나누고, 나머지는 한 디렉터리 안에서 `Loadable` 로 끊습니다.

## 정리

Unity 6.6 의 Content Directories 는 **Player 에 넣는 로컬 콘텐츠만 다룹니다.**

- 원격 배포는 없고 Unity 7 세대에 예고만 됐습니다. 원격 콘텐츠는 AssetBundle 로 남겨 섞어 씁니다.
- 섞으면 공유 에셋이 양쪽에 따로 빌드되고, Addressables 의 중복 수에는 안 잡힙니다. 디렉터리끼리도 중복을 못 지웁니다.
- Addressables 4.x 는 그룹만 바꾸면 로드 코드 그대로 옮겨지지만, 활성 빌더가 AssetBundle 전용이면 경고 없이 빈 빌드가 됩니다.