TOOLS & SYSTEMS / 기술 노트

직접 참조·Resources·AssetBundle·Addressables 선택하기

참조, 로드, 인스턴스와 해제 책임을 나누고 네 가지 자원 접근 방식의 선택 조건을 비교한다. 같은 프리팹을 대상으로 한 예제·실습·풀이를 담았다.

로드 API보다 먼저 수명과 배포를 정한다

에셋 관리, 리소스 시스템, 에셋 번들, 개발 고려 사항을 하나의 선택 문제로 정리했다. 노션에 적힌 검토 항목은 완료한 마이그레이션 성과로 쓰지 않는다.

프리팹을 가리키는 참조, 에셋을 준비하는 로드, 장면에 객체를 만드는 Instantiate, 사용을 끝내는 해제는 서로 다른 동작이다. 로드된 프리팹 하나로 인스턴스를 열 개 만들 수 있다. 인스턴스를 하나 지웠다고 프리팹의 모든 참조가 사라지는 것도 아니다.

방식접근 기준별도 배포관리해야 할 책임
직접 참조직렬화된 객체 참조기본적으로 Player 구성에 따름씬·프리팹 의존성
ResourcesResources 아래 상대 경로해당 자원은 Player에 포함경로·포함량·로드한 참조
AssetBundle번들 파일과 에셋 이름가능의존 번들·버전·로드·언로드
Addressables주소·GUID·Label 등 키구성에 따라 로컬·원격카탈로그·핸들·빌드·배포

어떤 방식이 무조건 빠르다고 결정하지 않는다. 런타임 검색 비용뿐 아니라 빌드 크기, 최초 다운로드, 패치 단위와 소유권을 함께 본다. 노트의 Resources 조회 복잡도 설명을 현재 모든 플랫폼의 보장으로 재사용하지 않았다.

같은 프리팹을 놓는 코드 비교

다음은 각 API를 보여 주는 독립 구간이며 하나의 함수에 합쳐 동시에 실행하지 않는다. 첫 두 예제는 MonoBehaviour 안의 필드·메서드다.

[UnityEngine.SerializeField] UnityEngine.GameObject prefab;
void PlaceDirect()
{
    if (prefab) UnityEngine.Object.Instantiate(prefab);
}
void PlaceResource()
{
    var loaded = UnityEngine.Resources.Load<UnityEngine.GameObject>("Study/Marker");
    if (loaded) UnityEngine.Object.Instantiate(loaded);
}

Resources 경로는 예를 들어 Assets/Resources/Study/Marker.prefab에서 확장자와 Resources 앞부분을 뺀 값이다. 포함된 모든 객체를 자동으로 화면에 생성하는 방식은 아니다. 직접 참조와 Resources에서 같은 에셋을 중복 관리하면 포함 관계를 별도 조사해야 한다. Unity Resources 문서

번들은 해당 플랫폼에서 미리 빌드한 파일을 전제로 한다. 파일 로드와 에셋 추출을 분리한다.

var bundle = UnityEngine.AssetBundle.LoadFromFile(bundlePath);
if (!bundle) throw new System.InvalidOperationException("bundle load failed");
var loaded = bundle.LoadAsset<UnityEngine.GameObject>(assetName);
if (!loaded) { bundle.Unload(false); throw new System.InvalidOperationException("asset missing"); }
var instance = UnityEngine.Object.Instantiate(loaded);
// bundle과 instance의 소유자가 사용 종료 순서를 관리한다.
// 종속 번들이 있다면 이를 먼저 준비해야 한다.

Unload(false)와 Unload(true)는 같은 정리가 아니다. 특히 살아 있는 인스턴스·재질이 사용하는 에셋을 강제로 제거하면 참조가 깨질 수 있다. 이 예제는 의존성 그래프를 자동 처리하지 않는다.

Addressables는 InstantiateAsync로 만든 인스턴스를 해당 작업의 계약에 따라 반환한다.

var handle = UnityEngine.AddressableAssets.Addressables.InstantiateAsync("study-marker");
try
{
    await handle.Task;
    if (handle.Status != UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
        throw new System.InvalidOperationException("instance load failed");
    // 여기에서 handle.Result를 사용하고 사용이 끝난 뒤 블록을 벗어난다.
}
finally
{
    if (handle.IsValid())
    {
        if (handle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
            UnityEngine.AddressableAssets.Addressables.ReleaseInstance(handle);
        else
            UnityEngine.AddressableAssets.Addressables.Release(handle);
    }
}

이는 Addressables 1.21 계열의 비-WebGL Task 예다. 성공 분기에서는 실제 사용이 끝난 뒤 ReleaseInstance한다. 사용 예를 짧게 보이려고 여기서는 곧바로 반환했다. LoadAssetAsync 후 일반 Instantiate를 사용하는 경우와 소유권 규칙이 다르다. 메모리 관리 문서

실습과 풀이

  1. ‘항상 쓰는 시작 UI’와 ‘주마다 교체하는 큰 스테이지’를 비교한다. 풀이: 시작 UI는 직접 참조로 단순하게 구성할 수 있고, 원격 갱신이 필요한 스테이지는 별도 콘텐츠 배포 구조를 검토한다. Addressables를 쓴다는 이유만으로 모든 자원이 원격화되지는 않는다.
  2. 에셋 하나를 두 번 로드하고 인스턴스 세 개를 만든다. 풀이: 로드 작업과 인스턴스마다 소유자를 표로 적는다. 일반 Instantiate는 Addressables 로드 참조를 자동으로 추가하지 않으므로 마지막 인스턴스 사용 전 에셋 핸들을 해제하지 않는다.
  3. 네 방식을 비교 측정한다면 조건은 무엇인가? 풀이: 같은 플랫폼·프리팹·의존성·캐시 상태를 사용한다. 다운로드·디스크 읽기·역직렬화·인스턴스 생성 시간을 나누며 콜드/웜 결과를 섞지 않는다.

확인한 범위

원문과 공개 문서를 대조한 선택 가이드다. Unity 컴파일·빌드·네 방식의 로딩·메모리·시간 비교는 실행하지 않았다. DCB의 로딩 구조를 변경하거나 누수를 해결한 기록이 아니다. 코드와 설명은 이 글을 위해 AI의 도움을 받아 새로 구성했다.

이어 읽기

전체 학습 순서 · 다음 글: GLFW·GLAD·컨텍스트와 첫 렌더 루프 만들기

← 글 모아보기