본문 바로가기

유니티/Addressable

[ Addressable ] 07 . 원격에서 에셋 로드하기

[ 학습 흐름 ]
  • 원격에서의 에셋 로드 과정을 이해한다
  • 과정 1 : 초기화
  • 과정 2 . 카탈로그
  • 과정 3 .에셋 번들의 다운로드
  • 과정 4 .에셋 로드

[ 어드레서블 에셋 원격 로드의 과정 ]

1 . 어드레서블 에셋 원격 로드

[ 어드레서블이 원격에서 에세을 로드하는 과정 ]

- 에셋 로드 메서드 중 하나를 호출하면 어드레서블 시스템은 다음 작업을 수행하는 비동기 작업을 시작한다 .

  1. IResourceLocation 키를 제외하고 지정된 키에 대한 리소스 위치를 조회 .
  2. 종속성 목록을 수집 .
  3. 필요한 원격 에셋 번들을 다운로드 .
  4. 에셋 번들을 메모리에 로드 .
  5. 작업의 Result 오브젝트를 로드된 오브젝트로 설정 .
  6. 작업의 Status를 업데이트하고 Completed 이벤트 리스너를 호출 .

- 로드 작업이 성공하면 Status가 Succeeded로 설정되고, 로드된 오브젝트는 Result 오브젝트에서 액세스할 수 있다. .

오류가 발생하면 예외가 작업 오브젝트의 OperationException 멤버에 복사되고 Status가 Failed로 설정된다.


[ 1 . 초기화 ]

1 . 초기화 작업

[ 초기화 ]

- 어드레서블 시스템은 어드레서블을 처음 로드하거나 어드레서블 API를 호출할 때 런타임에 자체적으로 초기화된다.

- 어드레서블을 더 일찍 초기화하려면 Addressables.InitializeAsync를 호출한다.

- 초기화가 이미 발생한 경우 이 메서드는 아무 작업도 수행하지 않는다.


[ 초기화의 과정 ]

초기화 작업에서는 다음과 같은 작업을 수행한다.

  • ResourceManager 및 ResourceLocators를 설정.
  • StreamingAsset에서 어드레서블이 생성하는 설정 데이터를 로드한다 .
  • 초기화 오브젝트 작업을 실행한다 .
  • 콘텐츠 카탈로그를 로드한다. 기본적으로 어드레서블은 먼저 콘텐츠 카탈로그의 업데이트를 확인하고 사용 가능한 경우 새 카탈로그를 다운로드한다 ( 카탈로그 관리가 자동인 경우).

[ 초기화 동작의 변경 ]

다음 어드레서블 설정으로 초기화 동작을 변경할 수 있다.


2 . 초기화 오브젝트

[ 초기화 오브젝트란 ]

- 어드레서블 에셋 설정에 오브젝트를 연결하여 런타임에 초기화 프로세스에 전달할 수 있다. 

- 예시중 하나로 CacheInitializationSettings 오브젝트를 생성하여 Unity의 Cache 설정을 런타임에 초기화할 수 있다.

- 고유한 유형의 초기화 오브젝트를 생성하려면 IObjectInitializationDataProvider 인터페이스를 구현하는 스크립터블 오브젝트를 생성한다.

- 이 오브젝트를 사용하여 어드레서블이 런타임 데이터와 함께 포함하는 ObjectInitializationData 에셋을 생성할 수 있다.


[ CacheInitializationSettings ]

CacheInitializationSettings 오브젝트를 사용하여 Unity의 Cache 설정을 런타임에 초기화할 수 있다 .

  1. CacheInitializationSettings 에셋을 생성한다(메뉴: Assets > Addressables > Initialization > Cache Initialization Settings).
  2. 프로젝트 패널에서 새 에셋 파일을 선택하여 인스펙터에서 설정을 확인한다.
  3. 설정을 원하는 대로 조정한다.
  4. 어드레서블 설정 인스펙터(메뉴: Window > Asset Management > Addressables > Settings)를 연다.
  5. 인스펙터의 Initialization Objects 섹션에서 + 버튼을 클릭하여 목록에 새 오브젝트를 추가한다.
  6. File 다이얼로그에서 CacheInitializationSettings 에셋을 선택하고 Open 을 클릭한다.
  7. 캐시 설정 오브젝트가 목록에 추가된다 .

런타임에 어드레서블이 초기화되면 이 설정이 기본 Unity Cache에 적용된다.

- 이 설정은 어드레서블 시스템에서 다운로드한 에셋 번들뿐만 아니라 기본 캐시에 있는 모든 에셋 번들에 적용된다. 

  • CacheDirectoryOverride : 캐시에 대한 사용자 정의 디렉터리를 지정한다
  • CompressionEnabled : 압축을 활성화하면 디스크 공간을 절약할 수 있지만 압축을 푸는 동안 성능에 영향을 미칠 수 있다.
  • ExpirationDelay : 에셋이 제거되기 전까지 캐시에 남아 있는 기간을 제어하려면 이 기능을 사용한다. 기본 150일 .
  • LimitCacheSize : 캐시 크기를 제한할지 여부를 나타냅니다.
  • MaximumCacheSize : 디스크 공간을 너무 많이 소모하지 않도록 최대 캐시 크기를 정의한다 .

추가후 아무런 동작도 하지 않는다면 추가하지 않은것과 같은 기본값이다 .


[ 2 . 카탈로그 확인 ]

1 . 런타임의 카탈로그 관리

[ 카탈로그의 관리 ]

- 기본적으로 어드레서블 시스템은 런타임에 카탈로그를 자동으로 관리한다.

- 원격 카탈로그를 사용했다면 어드레서블 시스템은 자동으로 새 카탈로그를 확인 , 새 버전을 다운로드해 메모리에 로드한다 .


2 . 카탈로그  수동으로 관리하기

[ 카탈로그의 수동 관리 ]

- 카탈로그의 확인 및 업데이트를 수동으로 해야하는 경우 다음의 처리가 필요하다 .

- 어드레서블 세팅에서 Only update catalogs manually를 체크한다 . 이제 카탈로그는 수동으로 관리된다 .


[ 카탈로그의 수동 관리 ]

IEnumerator CheckCatalogs()
{
    List<string> catalogsToUpdate = new List<string>();
    AsyncOperationHandle<List<string>> checkForUpdateHandle
        = Addressables.CheckForCatalogUpdates();
    checkForUpdateHandle.Completed += op => { catalogsToUpdate.AddRange(op.Result); };

    yield return checkForUpdateHandle;

    if (catalogsToUpdate.Count > 0)
    {
        AsyncOperationHandle<List<IResourceLocator>> updateHandle
            = Addressables.UpdateCatalogs(catalogsToUpdate);
        yield return updateHandle;
        Addressables.Release(updateHandle);
    }

    Addressables.Release(checkForUpdateHandle);
}

- 위 스크립트로 업데이트할 카탈로그가 있다면 업데이트를 시행한다  

 


[ 3 . 에셋 번들 다운로드 ]

1 . 에셋 번들 캐싱하기

[ 어에셋 번들의 캐싱 ]

- 그룹의 Use Asset Bundle Cache가 활성화된 경우 , 그룹에서 빌드된 에셋 번들은 다운로드후 클라이언트 기기에 캐시된다 .

- 캐시된 번들은 업데이트 혹은 캐시에서 삭제된 경우에만 다시 다운된다 .


[ 불필요한 캐시 삭제하기 ]

- 기기에 불필요한 캐시 데이터가 있는 경우 다음 옵션 중 하나를 선택할 수 있다.


[ 종속성 미리 로드하기 ]

IEnumerator IEDownloadAsset()
    {
        AsyncOperationHandle operationHandle = Addressables.DownloadDependenciesAsync("default");
        State = eState.Downloading;
        yield return operationHandle;
        AssetBundle.UnloadAllAssetBundles(true);
        Addressables.Release(operationHandle);
        yield return IELoadMemory();
    }

- 위 메서드는 default 레이블을 가진 에셋이 있는 종속성 (에셋번들등 )을 미리 로드하여 성능을 개선 할 수 있다 .

- 게임을 처음 시작할 때 필수 콘텐츠를 다운로드하여 사용자가 게임플레이 도중 콘텐츠 다운로드를 기다릴 필요가 없다.

- 번들 다운로드후 구버전의 에셋 번들을 메모리에서 언로드한다 .

 


[ 4 . 어드레서블 에셋 로드 ]

1 . 어드레서블 에셋 로드

[ 에셋 로드 키 ]

- 단일 키 또는 키 목록을 로드 메서드에 전달함으로 로드할 에셋을 식별한다 .

- 키는 다음 오브젝트 중 하나가 될 수 있다 .

  • Address : 에셋에 할당된 주소가 포함된 문자열
  • Label : 하나 이상의 에셋에 할당된 레이블을 포함하는 문자열
  • Asset Referecne 오브젝트 :  Asset Reference 의 인스턴스
  • IResourceLocation 인스턴스 : 에셋 및 그 종속성을 로드하는 방법을 설명하는 중간객체

2 . 어드레서블이 에셋을 로드하는 방법

[ 에셋 로드 과정 ]

  1. IResourceLocation 키를 제외하고 지정된 키에 대한 리소스 위치를 조회한다 .
  2. 종속성 목록을 수집한다 
  3. 필요한 원격 에셋 번들을 다운로드 한다 .
  4. 에셋 번들메모리에 로드한다 
  5. 작업의 Result 오브젝트를 로드된 오브젝트로 설정한다 .
  6. 작업의 Status을 업데이트하고 Completed 이벤트 리스너를 호출한다 .

3 . 로드된 에셋과 키 연결하기

[ 에셋 로드 과정 ]

- 개별 에셋을 로드하는 순서는 로드 메서드에 전달하는 목록의 키 순서와 다를 수 있다 .

- 만약 에셋을 로드할때 키와 에셋을 연결해야 한다면 다음의 단계를 거칠 수 있다 .

  1. 에셋 키 목록을 사용 , IResourceLocatoin 인스턴스를 로드한다 .
  2. IResourceLocation 인스턴스를 키로 사용하여 개별 에셋을 로드한다 .

- 아래 예시는 IResourceLocation 오브젝트는 키 정보를 포함 , 키와 에셋을 연관시키기 위해 Dictionary를 사용한다 .


[ 키워드 ]

  •  AsyncOperationHandle : 비동기 작업을 나타내는 Unity의 Addressables 시스템에서 제공하는 구조체. 비동기 작업의 진행 상황과 완료를 추적하는 데 사용됨 . 다음의 정보가 포함된다 .
    • Status: 작업의 현재 상태(예: None, Succeeded, Failed )
    • IsDone: 작업이 완료되었는지 여부를 나타내는 부울
    • Result: 작업이 성공적으로 완료된 경우 작업의 결과
    • Completed: 구독할 수 있는 이벤트로, 작업이 완료되면 호출
  • IResourceLocation : Addressables 시스템에서 리소스의 위치를 ​​나타내는 인터페이스.다음의 정보가 포함된다 . 
    • PrimaryKey: 리소스를 식별하는 데 사용되는 키.
    • InternalId: 리소스 위치에 대한 내부 식별자
    • ProviderId: 리소스를 로드할 공급자의 ID
    • Dependencies: 이 리소스가 의존하는 다른 리소스 위치의 목록
    • ResourceType: 리소스 유형(예: Texture2D, AudioClip).
  • CreateGenericGroupOperation :여러 비동기 작업을 관리할 수 있는 그룹 작업을 생성한다 . 이를 통해 여러 비동기 작업을 단일 단위로 처리할 수 있으므로 동시에 여러 작업 완료를 더 쉽게 조정하고 처리할 수 있다.

[ 스크립트 ]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Events;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;

internal class LoadWithLocation : MonoBehaviour
{
    //에셋 로드후 키 - 핸들이 담기는 딕셔너리
    public Dictionary<string, AsyncOperationHandle<Texture2D>> operationDictionary;
    //로드에 사용할 키 . 레이블로 설정하였다 .
    public List<string> keys=new List<string> { default,"Data"};
    //모든 에셋 로드가 완료되면 실행할 이벤트
    public UnityEvent Ready;

    IEnumerator LoadAndAssociateResultWithKey(IList<string> keys)
    {
        if (operationDictionary == null)
            operationDictionary = new Dictionary<string, AsyncOperationHandle<Texture2D>>();

        //지정된 키에 대한 리소스 위치를 로드하는 비동기 작업
        AsyncOperationHandle<IList<IResourceLocation>> locations
            = Addressables.LoadResourceLocationsAsync(keys,
                Addressables.MergeMode.Union, typeof(Texture2D));

        yield return locations;

        var loadOps = new List<AsyncOperationHandle>(locations.Result.Count);
        //locations를 순환하며 에셋을 비동기적으로 로드
        foreach (IResourceLocation location in locations.Result)
        {
            AsyncOperationHandle<Texture2D> handle =
                Addressables.LoadAssetAsync<Texture2D>(location);
            handle.Completed += obj => operationDictionary.Add(location.PrimaryKey, obj);
            loadOps.Add(handle);
        }
        // 비동기작업을 그룹으로 관리
        yield return Addressables.ResourceManager.CreateGenericGroupOperation(loadOps, true);

        Ready.Invoke();
    }

    void Start()
    {
        Ready.AddListener(OnAssetsReady);
        StartCoroutine(LoadAndAssociateResultWithKey(keys));
    }

    private void OnAssetsReady()
    {
        float x = 0, z = 0;
        foreach (var item in operationDictionary)
        {
            Debug.Log($"{item.Key} = {item.Value.Result.name}");
            ////Instantiate(item.Value.Result,
            ////    new Vector3(x++ * 2.0f, 0, z * 2.0f),
            ////    Quaternion.identity, transform);
            //if (x > 9)
            //{
            //    x = 0;
            //    z++;
            //}
        }
    }

    private void OnDestroy()
    {
        // 에셋이 필요하지 않을때 해제
        foreach (var item in operationDictionary)
        {
            Addressables.Release(item.Value);
        }
    }
}