본문 바로가기

내일 배움 캠프/뭐하지

Addressable

[Addressable]

  Unity의 리소스 관리 시스템

  프로젝트에 포함된 에셋(텍스처, 프리팹, 오디오 등)을 주소 기반으로 관리하며 비동기 로딩, 메모리 최적화, 빌드 분리 등의 장점을 제공한다.

 

핵심 개념

    ● 주소만 있으면 어디에 있어도 사용 가능

        ○ 에셋을 Addressable로 등록하고 '주소 문자열' 하나만 알고 있으면 로드 가능

        ○ 에셋이 Resources 폴더, StreamAssets, 원격 서버, CDN 어디에 있든 상관없이 사용 가능

Addressables.LoadAssetAsync<GameObject>("MyCharacter");

 

    ● 비동기 로드 지원

        ○ Addressables.LoadAssetAsync는 비동기 방식으로 작동해 게임의 로딩 지연을 줄임

        ○ yield return 혹은 async/awit와 조합하여 자연스러운 흐름 구현 가능

// Coroutine 예시
IEnumerator Start()
{
    var handle = Addressables.LoadAssetAsync<GameObject>("MyEffect");
    yield return handle;

    GameObject obj = handle.Result;
    Instantiate(obj);
}

 

    ● 메모리 자동 관리

        ○ 사용하지 않는 에셋은 Unload 처리 가능

        ○ Addressable.Release(handle)을 호출하면 참조를 제거하고 필요 시 메모리에서 해제

        ○ 더 이상 사용하지 않는 리소스를 명시적으로 해제해서 메모리 누수 방지

Addressables.Release(handle); // 메모리에서 언로드

 

    ● 그룹으로 에셋 관리

        ○ 에셋은 Addressable Group으로 분류 가능

            ○ 예시: UI, Enemy, Stage1Resources 등

        ○ 그룹별로 빌드 설정, 로딩 방식, 배포 경로를 달리할 수 있음

            ○ Local, Remote, Streaming 등

 

    ● 빌드 시 에셋을 번들로 분리

        ○ Addressables 빌드 시 자동으로 에셋을 AssetBundle 형태로 압축

        ○ 필요할 때만 다운로드 → 최초 용량 최소화 + 업데이트 용이

        ○ Patch, Hotfix 구현 시 유용

 

    ● Preload / Lazy Load 등 전략 가능

        ○ 특정 에셋 그룹에 Preload 라벨을 붙이면 게임 시작 시 미리 로딩

        ○ 리소스가 많은 게임에서 초기 로딩 시간과 성능의 균형 조절 가능

 

Addressables vs 기존 Resources의 차이점

항목 Addressables Resources
로딩 방식 비동기 동기
메모리 관리 자동 관리 가능 직접 관리 어려움
빌드 전략 개별 번들로 분리 가능 모든 에셋 포함
원격 다운로드 가능 불가능
유연성 매우 높음 낮음
런타임 로드 주소 기반 경로 기반 (Resources.Load)

 

언제 Addressable을 써야 할까?

    ● 리소스가 많고 최적화가 중요한 프로젝트

    ● 런타임에 리소스를 비동기로 로드해야 할 경우

    ● DLC, 패치, 원격 업데이트, 콘텐츠 스트리밍이 필요한 경우

 


 

[Addressable Profiles] 

빌드 시 에셋의 저장 위치와 로드 위치를 설정할 수 있는 템플릿

    ● BuildPath: 에셋 번들이 생성되어 저장되는 물리적 위치

    ● LoadPath: 런타임에 게임이 해당 에셋을 로드하려고 접근하는 URL/경로

 

Profile 종류

이름 설명
Build-in 번들이 앱 내에 포함됨
→ 무조건 로컬에서 로딩
Remote 번들이 원격 서버에 저장됨
→ HTTP/HTTPS로 다운로드 필요
Editor Hosted Unity 에디터가 간이 HTTP 서버를 띄워서 에셋을 제공
→ 로컬 테스트에 유용
Cloud Content Delivery (CCD) Unity의 클라우드 서버를 활용하여 배포
→ 유니티 CDN 주소 자동 연결
Custom 직접 주소를 작성하여 사용자 정의 서버에 연결
http://yourserver.com/... 처럼 직접 입력 가능

 

BuildTarget

    ● StandaloneWindows64, Android, iOS, WebGL 등 Unity의 빌드 플랫폼 이름이 그대로 사용됨

    ● [BuildTarget] 변수는 자동으로 현재 플랫폼에 맞는 폴더 이름으로 치환됨

 

중요한 설정

    ● HTTP 접근 허용

    ● LoadPath가 HTTP라면 반드시 Player Settings에서 Allow HTTP를 활성화해야 함

    ● Project Settings > Player > Other Settings > Configuration > Allow downloads over HTTP

 


 

[Addressable 꿀팁]

이름 간단하게 변경

        ○ 해당 에셋 우클릭 > Simplify Addressable Names

 


 

[Addressable 원격 테스트 설정]

씬 또는 에셋을 Addressable로 등록

    ● 씬을 Build Settings에 추가한 뒤 Project 창에서 해당 씬의 Inspector > Addressable 체크

    ● 원하는 그룹으로 이동

 

■ Group 설정: Content Packing & Loading 스키마 추가

    ● Addressable Groups > 해당 group 선택

    ● 상단 Add Schema > Content Packing & Loading 추가

 

■ Build & Load Paths를 Remote로 설정

    ● Content Packing & Loading 섹션에서 

        ○ Build Path: RemoteBuildPath

        ○ Load Path: RemoteLoadPath

    ● 기본 Remote 경로

        ○ Build Path: ServerData/[BuildTarget]

        ○ LoadPath: http://localhost:yourport/[BuildTarget]

 

■ Addressable Asset Settings에서 카탈로그도 Remote로 설정

    ● Inspector > AddresableAssetSettings.asset 선택

    ● Build Remote Catalog 체크

    ● BuildPath & LoadPath Remote로 설정

 

■ Project SEttings > HTTP 허용 설정

    ● Edit > Project Settings > Player > Other Settings

    ● Allow downloads over HTTP를 Always allowed로 설정

    ● 개발할 때만 사용하기

 

■ Addressables Hostring Service 설정

    ● Window > Asset Management > Addressables > Hosting Services

    ● Port 재설정

    ● Enable 체크하여 로컬 HTTP 서버 켜기

 

■ Play Mode Script 설정

    ● Addressable Groups 창 > 상단의 Play Mode Script

    ● Use Existing Build (Widnows) 선택 → 실제 번들을 사용하고 싶을 때 선택

 

■ Adddressables Build 실행

    ● Addressables Groups > 상단 Build > New Build > Defualt Build Script 선택

    ● 실제 RemoteBuildPath에 번들이 생성됨

    ● catalog.json이 포함됨

 

■ 번들 확인

    ● 프로젝트 루트: ServerData/StandaloneWindows64 폴더 내 확인

    ● .bundle, .json 파일 등이 포함되어 있어야 함

    ● 에디터 테스트 시 이 경로가 웹서버 루트가 됨


[Addressable 예시]

 에셋 로드

    ● Addressable 

    ● LoadAssetManager

public class LoadAssetManager : MonoBehaviour
{
    //어드레서블 주소
    [SerializeField] private string _assetName = "Warrior Red";
    private GameObject _warriorInstance;

    //비동기 로딩 시 사용할 핸들
    private AsyncOperationHandle<GameObject> _loadHandle;
    
    //비동기 로드 메소드
    public void LoadWarriorAsync()
    {
        //비동기 함수이기 때문에 끝났을 때 끝났을 때 호출 함수를 콜백으로 받아오기
        Addressables.LoadAssetAsync<GameObject>(_assetName).Completed +=
            (AsyncOperationHandle<GameObject> handle) =>
            {
                _loadHandle = handle;
                //로드된 에셋의 인스턴스를 생성
                OnWarriorLoad();
            };
    }

    private void OnWarriorLoad()
    {
        if (_loadHandle.Status == AsyncOperationStatus.Succeeded)
        {
            var warrior = _loadHandle.Result;
            _warriorInstance = Instantiate(warrior, Vector3.zero, Quaternion.identity);
            Debug.Log("전사 생성");
        }
    }

    //해제
    public void ReleaseWarrior()
    {
        //어드레서블 메모리 해제
        Addressables.Release(_loadHandle);
        
        //생성한 인스턴스 삭제
        Destroy(_warriorInstance);
    }
}

 

    ● LoadAssetManagerEditor

[CustomEditor(typeof(LoadAssetManager))]
public class LoadAssetManagerEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        LoadAssetManager manager = (LoadAssetManager)target;

        if (GUILayout.Button("전사 로드"))
        {
            manager.LoadWarriorAsync();
        }

        if (GUILayout.Button("전사 해제"))
        {
            manager.ReleaseWarrior();
        }

    }
}

 

■ 씬 로드

    ● LoadSceneManager

public class LoadSceneManager : MonoBehaviour
{
    [SerializeField] private string sceneName = "Level01";

    public async void DownloadAndLoadAsync()
    {
        await CheckDownloadSize();
        await Task.Delay(2000);
        await LoadSceneAsync();
    }
    
    //캐시 클리어
    public void ClearCache()
    {
        Addressables.ClearDependencyCacheAsync(sceneName);
        Debug.Log("캐시 삭제");
    }
    
    //다운로드 사이즈 체크
    public async Task CheckDownloadSize()
    {
        //파일 사이즈 체크
        AsyncOperationHandle<long> handle = Addressables.GetDownloadSizeAsync(sceneName);
        //Task 작업이 끝날 때까지 기다리는 것 (다른 작업들을 lock 거는 것이 아니라 비동기 방식으로 진행)
        await handle.Task;

        if (handle.Status == AsyncOperationStatus.Succeeded)
        {
            long size = handle.Result / (1024 * 1024); //메가바이트로 변환
            Debug.Log($"다운로드 사이즈 {size}MB");
        }
        
        //핸들 해제
        Addressables.Release(handle);
    }
    
    //씬 로딩
    public async Task LoadSceneAsync()
    {
        try
        {
            var loadHandle = Addressables.LoadSceneAsync(sceneName);
            while (!loadHandle.IsDone)
            {
                Debug.Log($"Progress: {loadHandle.PercentComplete * 100}%");
                await Task.Yield();
            }
            Debug.Log($"Status: {loadHandle.Status}");
        }
        catch (System.Exception e)
        {
            Debug.Log(e.Message);
        }
    }
}

 

    ● LoadSceneManagerEditor

[CustomEditor(typeof(LoadSceneManager))]
public class LoadSceneManagerEditor : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();
        var manager = (LoadSceneManager)target;

        if (GUILayout.Button("씬 로드"))
        {
            manager.DownloadAndLoadAsync();
        }

        if (GUILayout.Button("캐시 삭제"))
        {
            manager.ClearCache();
        }
    }
}

'내일 배움 캠프 > 뭐하지' 카테고리의 다른 글

저작권  (3) 2025.06.22
UniTask, UniRx  (0) 2025.06.22
직렬화  (0) 2025.06.11
코드 최적화 & 프로파일러  (0) 2025.06.10
게임 빌드 프로세스  (0) 2025.06.05