Abstract
이 에디션의 리소스 계층은 파일 두 개가 전부입니다 — ResourceComponent.cs 와 ResourceManager.cs. 번들 관리·의존성·버전·패치는 전부 Unity 의 Addressables 에 맡기고, 프레임워크 쪽에는 "코어가 기대하는 인터페이스를 Addressables 호출로 바꿔 주는 어댑터"만 두었습니다. 게임 코드가 기억할 규칙은 하나입니다 — 에셋 이름 = Addressables 주소.
실무에서 이 페이지가 필요한 순간
•
ShowEntity 를 불렀는데 아무것도 안 뜰 때 — 주소가 어떻게 해석되는지 알아야 원인을 찾는다
•
메모리가 안 줄어든다고 느낄 때 — 에셋이 언제 해제되는지 알아야 한다
•
원격 배포나 다른 로더로의 교체를 검토할 때 — 어디를 고치면 되는지 알아야 한다
ToyboxNightmare 에서는
게임 코드가 ResourceComponent 를 직접 부르는 곳은 0곳입니다. 캐릭터·적·투사체·이펙트는 ShowEntity, HUD 는 OpenUIForm, 효과음은 PlaySound 가 안에서 대신 부릅니다. Addressables 에는 Prefabs 그룹 하나에 25개가 짧은 주소(Girl, Zombunny, HUDForm, PlayerHurt …)로 등록돼 있습니다.
게임 코드에서의 사용법은
Resource Management System, 로딩이 일어나는 순서는
Resource Loading(리소스 로딩)-Addressable, 패치는
Resource Updating(리소스 업데이트)-Addressable 를 보세요.
구조
graph TD
E["EntityManager / UIManager / SoundManager<br>SceneManager / DataTable / Config / Localization<br>(코어)"]
E -->|"IResourceManager 인터페이스"| R
R["ResourceManager<br>(UnityGameFramework.Runtime)"]
R --> A["Addressables"]
C["ResourceComponent"] -.->|"생성해서 각 모듈에 주입"| RMermaid
복사
코어의 모듈들은 IResourceManager 라는 인터페이스만 압니다. "이름을 주면 콜백으로 에셋이 온다"는 약속만 지켜지면 그 뒤가 무엇이든 상관하지 않습니다. 이 에디션은 그 인터페이스의 구현체를 Addressables 용으로 작성해 끼웠고, 코어의 엔티티·UI·사운드 코드는 한 줄도 바꾸지 않았습니다.
초기화 순서
1.
ResourceComponent.Awake — ResourceManager 를 만든다.
2.
EntityComponent, UIComponent, SoundComponent, SceneComponent, DataTableComponent, ConfigComponent, LocalizationComponent 의 Start — 각자 ResourceComponent.ResourceManager 를 꺼내 자기 모듈에 SetResourceManager(...) 로 주입한다.
3.
Addressables 자체는 첫 로드 요청 때 스스로 초기화된다. 별도의 "리소스 초기화 완료 대기" 단계가 없다.
그래서 GameFramework.prefab 에는 Resource 오브젝트가 반드시 있어야 합니다. 없으면 엔티티·UI·사운드 컴포넌트가 Start 에서 실패합니다.
IResourceManager 가 Addressables 의 무엇으로 바뀌나
프레임워크 호출 | 실제로 일어나는 일 |
LoadAsset(name, callbacks) | Addressables.LoadAssetAsync<object>(name). 성공하면 핸들을 주소별 목록에 보관하고 성공 콜백 호출 |
UnloadAsset(asset) | 그 에셋의 주소를 찾아 보관한 핸들 하나를 Addressables.Release |
LoadScene(name, callbacks) | Addressables.LoadSceneAsync(name, LoadSceneMode.Additive) |
UnloadScene(name, callbacks) | 보관한 씬 핸들로 Addressables.UnloadSceneAsync |
InitResources(callback) | 아무것도 하지 않고 즉시 콜백 |
HasAsset(name) | 항상 "있음"(AssetOnDisk). 실제 존재 여부를 확인하지 않는다 |
LoadBinary(...) | 즉시 실패 콜백. 바이너리는 TextAsset(.bytes)으로 등록해 LoadAsset 으로 읽는다 |
버전 확인·검증·업데이트·리소스 그룹 계열 | 빈 구현. 패치는 Addressables API 로 직접 한다 |
핸들 관리
Addressables 에는 "로드한 횟수만큼 Release 해야 메모리에서 내려간다"는 규칙이 있습니다. 같은 프리팹을 세 번 로드하면 핸들이 셋 생기고, 셋 다 Release 해야 합니다. 프레임워크가 감춰 주는 가장 큰 일이 이 짝 맞추기입니다.
ResourceManager 는 표 두 개를 듭니다.
// 주소 → 그 주소로 로드해서 받은 핸들들
Dictionary<string, List<AsyncOperationHandle>> mAssetHandles;
// 에셋 객체 → 주소 (UnloadAsset 은 객체만 넘겨받으므로 역방향 조회가 필요하다)
Dictionary<object, string> mAssetNames;
C#
복사
시점 | 동작 |
로드 성공 | 주소의 핸들 목록에 핸들을 추가하고, 에셋 → 주소 대응을 기록한다 |
로드 실패 | 실패한 오퍼레이션을 즉시 Release 하고 실패 콜백을 부른다. (안 하면 잘못된 주소를 반복 요청할 때마다 핸들이 쌓인다) |
UnloadAsset(asset) | 에셋으로 주소를 찾고, 그 주소의 핸들 목록에서 마지막 하나만 꺼내 Release 한다. 목록이 비면 두 표에서 항목을 지운다 |
게임 코드는 이 표를 볼 일이 없습니다. 엔티티라면 "인스턴스가 새로 만들어질 때 로드 1회, 그 인스턴스가 파괴될 때 해제 1회"가 자동으로 맞춰집니다.
ResourceComponent 가 추가로 하는 일
Addressables 와 별개로, 주기적으로 Resources.UnloadUnusedAssets() 를 호출해 어디서도 참조하지 않는 에셋을 정리합니다.
인스펙터 필드 | 기본값 | 뜻 |
Max Unload Unused Assets Interval | 300초 | 아무 요청이 없어도 이 간격마다 한 번 정리한다 |
Min Unload Unused Assets Interval | 60초 | UnloadUnusedAssets(...) 로 예약해도 직전 정리 후 이만큼은 기다린다 |
ResourceComponent resource = GameEntry.GetComponent<ResourceComponent>();
resource.UnloadUnusedAssets(true); // 다음 기회에 정리 + GC.Collect (스테이지 전환 직후에 적당)
resource.ForceUnloadUnusedAssets(true); // 간격을 무시하고 다음 프레임에 바로
C#
복사
정리는 순간적으로 끊김을 만들 수 있으므로, 로딩 화면처럼 멈춰도 티가 안 나는 시점에 부르는 것이 좋습니다.
알아 둘 동작
•
LoadAsset 의 assetType 과 priority 인자는 무시됩니다. 주소만으로 로드합니다.
•
진행률 콜백과 의존 에셋 콜백이 불리지 않습니다. 따라서 ShowEntityUpdateEventArgs, OpenUIFormDependencyAssetEventArgs 같은 이벤트는 발생하지 않고, 성공·실패 이벤트만 옵니다.
•
실패 사유는 항상 LoadResourceStatus.NotExist 로 보고됩니다. 구체적인 원인은 함께 오는 오류 메시지 문자열에 있습니다.
•
씬은 항상 Additive 로 로드됩니다. 프레임워크가 사는 씬은 그대로 두고 콘텐츠 씬을 얹었다 내리는 구조입니다.
•
같은 씬을 ResourceComponent.LoadScene 으로 직접 두 번 로드하면 나중 핸들이 앞의 것을 덮어씁니다. SceneComponent 를 통하면 코어가 중복 로드를 먼저 막아 줍니다.
•
로드 중인 작업 목록 조회(GetAllLoadAssetInfos)는 항상 빈 결과입니다.
에디터에서 할 일
프레임워크가 대신하는 것은 런타임 코드뿐이고, 에셋 등록은 직접 합니다.
1.
Window → Asset Management → Addressables → Groups 를 연다.
2.
에셋(프리팹, 오디오 클립, 텍스트 에셋, 씬)을 그룹에 끌어다 놓는다.
3.
주소를 짧은 키로 바꾼다. 기본값은 Assets/.../Zombunny.prefab 같은 전체 경로이므로 Zombunny 로 줄인다. 여러 개를 선택해 우클릭 → Simplify Addressable Names 로 한 번에 할 수 있다. (씬만 예외 — Assets/ 로 시작하고 .unity 로 끝나는 전체 경로를 그대로 둔다.)
4.
Groups 창의 Play Mode Script 를 Use Asset Database (fastest) 로 둔다. 번들을 빌드하지 않고 바로 실행하기 위한 개발용 설정이며, Library/ 에 저장되어 git 으로 공유되지 않는다. 프로젝트를 받은 사람마다 한 번씩 해야 한다.
5.
번들은 Build → New Build → Default Build Script 로 만든다. 기본 설정에서는 플레이어를 빌드할 때 함께 빌드된다(Addressables Settings 의 Build Addressables on Player Build).
주소 문자열은 코드 곳곳에 흩뿌리지 말고 상수 클래스에 모으세요. 예제는 UITable, SoundTable, EnemyTable 에 모아 두었습니다. 오타가 컴파일 에러가 아니라 "조용한 로드 실패"가 되기 때문입니다.
확장 가능성
•
원격 배포·패치 — 프레임워크를 건드리지 않고 Addressables 쪽에서 합니다. 그룹을 Remote 로 바꾸면 같은 ShowEntity("Zombunny", …) 호출이 원격 번들을 받아 옵니다.
•
다운로드 진행률 — 이 구현은 진행률을 전달하지 않으므로, 로딩 화면에서 Addressables.GetDownloadSizeAsync / DownloadDependenciesAsync 를 직접 불러 미리 받아 둡니다.
•
다른 로더로 교체 — IResourceManager 를 구현한 클래스를 하나 더 만들어 ResourceComponent.Awake 에서 바꿔 끼우면 됩니다. ResourceManager.cs 는 원작 코드가 아니라 이 에디션에서 새로 쓴 파일이라, 프레임워크에서 고쳐도 원작과의 차이를 걱정할 필요가 없는 유일한 파일입니다.
•
직접 로드 — 엔티티·UI·사운드가 아닌 에셋(ScriptableObject, 텍스처 등)이 필요하면 ResourceComponent.LoadAsset 을 직접 부릅니다. 받은 에셋은 다 쓴 뒤 UnloadAsset 으로 로드한 횟수만큼 돌려줍니다.
resource.LoadAsset("EnemyStats", new LoadAssetCallbacks(
(assetName, asset, duration, userData) => { mStats = (EnemyStatsAsset)asset; },
(assetName, status, errorMessage, userData) => { Log.Error("로드 실패: {0}", errorMessage); }));
// 다 썼으면
resource.UnloadAsset(mStats);
C#
복사




