Deff_Dev

[Unity/C#] 어드레서블 (Addressable) 본문

Unity(유니티)/유니티 공부

[Unity/C#] 어드레서블 (Addressable)

Deff_a 2024. 7. 6. 17:07

모바일 게임을 빌드할 때, 게임에 들어가는 모든 리소스를 빌드 파일에 포함시킨다면 어떤 일이 벌어질까?

 

물론 2D 게임의 경우 리소스 용량이 크지 않아 큰 문제가 되지 않을 수 있다.

하지만 3D 게임에서는 이야기가 달라진다.

 

기본적으로 3D 모델은 용량이 크고 무겁기 때문에, 게임에 필요한 모든 리소스를 빌드 파일에 포함시키면, 빌드 파일의 용량은 급격히 커질 것이다.

 

예전에는 구글 플레이스토어의 업로드 용량 제한이 200MB였지만, 지금은 2GB로 늘어나 조금은 여유가 생겼다.

하지만 여전히 볼륨이 있는 게임을 업로드하기에는 부담스러운 용량 제한이다.

 

이 때문에 많은 게임 개발자들은 게임에 들어가는 리소스를 빌드 파일에 포함시키지 않는 방법을 선택하고 있다.

모바일 게임을 설치하고 처음 실행할 때 추가 다운로드 창을 본 적이 있을 것 이다.

 

이는 빌드 시 리소스를 포함시키지 않고, 마켓에 올린 후 플레이어가 게임을 다운로드하고 실행할 때 필요한 리소스를 추가로 다운로드 받도록 하는 방식이다.

 

유니티에서 이러한 방식을 구현할 때 주로 사용하는 방법은 Resources 폴더, 에셋 번들, 어드레서블이다

 
Resources 폴더는 관리가 편하지만 성능 이슈 때문에 현재 거의 사용하지 않는다.
 
에셋 번들은 리소스 압축하는 방식으로, 선택된 리소스와 관련된 나머지 리소스까지 전부 압축하는 방식이다.
사용하기가 어렵고 관리가 너무 불편하고 번들 빌드시에도 에러가 종종 발생하기도 한다.
어드레서블이 나오기 전 에셋 번들 관리 툴과 프레임 워크를 미리 만들어 놓은 회사들은 에셋 번들을 사용하는 경우가 많다고 한다.

에셋 번들 - Unity 매뉴얼

에셋 번들 은 플랫폼별 비코드 에셋(예: 모델, 텍스처, 프리팹, 오디오 클립, 씬 전체)이 들어 있는 아카이브 파일이며 런타임 시점에 로드할 수 있습니다. 에셋 번들에는 서로 종속성을 표시할

docs.unity3d.com

 
이 글에서는 에셋 번들보다 장점이 많고 편리한 어드레서블에 대해 얘기해보겠다.


어드레서블 

어드레서블 개요 | Addressables | 1.21.17

어드레서블 개요 어드레서블은 프로젝트에 맞게 확장할 수 있는 시스템을 제공합니다. 간단한 설정으로 시작한 다음, 프로젝트의 복잡성이 증가함에 따라 최소한의 코드만 변경하여 재구성할

docs.unity3d.com

 
어드레서블이란 ?

  • 에셋 번들이 가진 문제점(관리, 사용)들을 개선해서 관리형 툴과 사용하기 편한 프레임 워크를 구축해서 만든 새로운 리소스 압축 시스템

어드레서블 특징

  • 로드할 대상이 되는 에셋과 에셋이 로드되는 위치 및 방식을 분리한다.
  • 씬 프리팹, 텍스트 에셋, 이미지, sprite까지 모든 에셋을 어드레서블로 표시하고 고유한 이름을 부여 가능하다.
  • 빌드 경로 및 로드 할 서버 주소까지 툴에서 세팅 후 세팅 된 정보기반으로 바로 사용한다.
  • 참조 횟수를 자동으로 계산해 자동으로 언로드한다.

어드레서블 VS 에셋 번들

  • 사용 및 빌드
    • 에셋 번들은 빌드 경로 및 서버 경로, 사용을 위한 매핑등을 사용자가 직접 구현해야 한다.
    • 어드레서블은 빌드 경로 및 서버 경로, 매핑등을 프레임워크에서 제공한다.
  • 리소스 등록
    • 에셋을 번들에 등록하는 과정이 제공되는 방법으로는 굉장히 불편해서 직접 등록하는 코드를 구현해야한다.
    • 어드레서블에 등록하는 과정이 폴더 드래그앤 드롭으로 가능하고, 폴더에 리소스가 추가될 경우 바로 반영된다.

어드레서블의 문제점

  • 폴더로 등록 할 경우 키 등록이 불가능하다.
  • 일일이 키 등록을 하려면 에셋 번들보다 힘들어진다.

▶ 그렇기 때문에 데이터 매핑을 직접하는 방식을 사용하면 사용성이 매우 높아진다.


예제

 
로컬에서 Spawn을 누르면 어드레서블에서 리소스를 불러오고 Realease를 누르면 불러온 에셋을 해제하는 것을 만들어 보겠다.
 

초기 설정

 
PackageManager에서 Addressable을 Install한다.

 
Addressable Groups 탭을 열고 Create Addressable Settings를 누른다.
 

 
Local Group을 누른 뒤, 인스펙터 창에서  Bulid & Load Paths Cache Clear Behavior를 위 사진과 같이 설정한다.

 
Bulid & Load Paths

 

  • Local Build Path 
    • 로컬 파일 시스템에 에셋 번들을 저장한다.
    • 일반적으로 개발 중에 사용되며, StreamingAssets 폴더나 빌드 폴더 등에 저장될 수 있다.
  • Remote Build Path
    • 원격 서버에 에셋 번들을 저장한다.
    • 주로 클라우드 스토리지나 CDN(Content Delivery Network) 같은 외부 서버에 저장되어, 게임 배포 시 네트워크를 통해 에셋을 로드할 수 있게 한다.

 

Cache Clear Behavior

  • Clear When Space is Needed in Cache
    • 변경 사항이 있어 번들을 다운로드할 때 기존 버전을 유지하다가, 캐시 공간이 부족할 경우에만 기존 번들을 삭제하고 새로운 번들을 다운로드합니다.
  • Clear When When New Version Loaded
    • 변경 사항이 있어 번들을 다시 다운로드할 때, 기존의 번들을 즉시 삭제하고 새로운 번들을 다운로드합니다.

 
Inspect Top Level Settings를 누른 뒤, Build 탭에 Unique Bundle IDs를 체크해준다.
 

Unique Bundle IDs

  • 어드레서블 시스템에서 번들을 생성할 때, 각 번들에 고유한 식별자를 부여하는 기능이다.
  • 체크 시 빌드 때 새롭게 추가된 리소스만 추가한다.

 
Send Profiler EventsBuild Remote Catalog를 체크한다.
 

Send Profiler Events

  • 이벤트 뷰어 창을 통해 메모리가 로드되거나 언로드되는 것을 확인할 수 있다.

Build Remote Catalog  

  • 어드레서블 시스템에서 카탈로그 파일을 빌드하고, 이를 원격 서버에 업로드하여, 실행 중인 애플리케이션이 원격 서버에서 카탈로그를 불러올 수 있도록 하는 기능이다.
  • 리소스를 동적으로 업데이트하고 관리할 수 있다.

 

등록

 
생성하고자하는 프리팹을 생성한 후, Addressable를 체크한다.
이때, Material도 따로 체크해줘야한다.

 
체크를 했다면 Group에 추가가 되어져 있는 것을 볼 수 있다.

 
그룹 추가 및 해당 그룹에 맞게 Asset들을 배치시켜주고 Label를 설정해준다.
 
여기서 그룹을 추가한다면 위에서 했던 그룹 설정을 동일하게 해줘야한다.
 

스크립트 작성

 

주요 어드레서블 타입

  • AssetReference: 특정 타입을 지정하지 않고, 일반적인 어드레서블 리소스를 참조한다.
  • AssetReferenceGameObject: GameObject 타입의 어드레서블 리소스를 참조한다.
  • AssetReferenceSprite: Sprite 타입의 어드레서블 리소스를 참조한다.
  • AssetReferenceT<T>: 제네릭 타입을 사용하여 특정 타입의 어드레서블 리소스를 참조한다.
  • AssetLabelReference : 레이블을 통해 여러 자산을 참조하는 데 사용된다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets; // using Addressable

public class AddressableManager : MonoBehaviour
{
    // 어드레서블 타입 선언
    [SerializeField] private AssetReferenceGameObject cubeObj;
    [SerializeField] private AssetReferenceGameObject planeObj;
    [SerializeField] private AssetReferenceSprite coinSprite;

    [SerializeField] private Image image;

    // 생성된 오브젝트들을 저장하는 리스트
    private List<GameObject> gameObjs = new List<GameObject>();

    void Start()
    {
        StartCoroutine(InitAddressable());
    }

    private IEnumerator InitAddressable()
    {
        var init = Addressables.InitializeAsync();
        yield return init;
    }

    public void Button_SpawnObject() // Load
    {
        cubeObj.InstantiateAsync().Completed += (obj) =>
        {
            gameObjs.Add(obj.Result);
        };

        planeObj.InstantiateAsync().Completed += (obj) =>
        {
            gameObjs.Add(obj.Result);
        };

        coinSprite.LoadAssetAsync().Completed += (img) =>
        {
            image.sprite = img.Result;
        };
    }

    public void Button_ReleaseObject() // Release
    {
        if(gameObjs.Count == 0)
        {
            return;
        }

        coinSprite.ReleaseAsset();
        image.sprite = null;
        
        for(int i = gameObjs.Count - 1; i >= 0; i--)
        {
            Addressables.ReleaseInstance(gameObjs[i]);
            gameObjs.RemoveAt(i);
        }
    }
}

 

 
해당 스크립트를 오브젝트에 붙히고 캐싱을 완료한 후, 각 버튼의 OnClick 이벤트에 해당 함수를 등록한다.

 
이제 실행해서 테스트를 해본다면, 잘 작동할 것 이다. 
 
 
어드레서블의 기본에 대해 공부를 해봤으니 이제 현재 진행하는 프로젝트에서 어드레서블을 이용해 리소스들을 관리할려고 한다.


 
[참고 영상]
https://www.youtube.com/watch?v=Z84GCeod_BM