Unity 어드레서블 애셋

정선호·2023년 7월 1일
0

Unity Features

목록 보기
9/28

참고 자료

공식 문서

참고 영상

로컬 사용

강의 영상

패키지 설치 및 초기 세팅

설치 및 창 오픈

Package Manager에서 Addressables을 임포트한다.
메뉴바의 Window - Asset Management - Addressables - Group을 눌러 Addressables Groups창을 연다.

Addressables Groups 창의 Profile - Manage Profiles를 선택해 Addressables Profiles창을 연다.
해당 창에서는 서버에서 에셋을 다운로드 할 때 사용할 서버 주소를 입력할 수 있다.

초기 세팅

Addressables Groups의 기본 로컬 그룹(Defalult Local Group(Default)) 인스펙터 창에서 Cache Clear BehaviourClear When New Version Loaded 선택

  • 설정 시 새 버전의 번들을 다운로드 할 때 이전 버전 번들을 자동으로 삭제

이후 Inspect Top Level Settings를 클릭해 다음과 같이 세팅

  • Unique Bundle IDs 체크(기존에 변경점이 없는 번들들은 무시하게 됨)
  • Send Profiler Events 체크(이벤트 뷰어를 활성화해 상황 확인 가능)
  • Build Remote Catalog 체크(카탈로그 사본을 생성해 서버에 업로드)
    • Build & Load PathsRemote로 설정

다시 기본 로컬 그룹에 들어와 다음과 같이 세팅

  • Build & Load PathsRemote로 설정

프리팹 등록 및 분류

오브젝트 프리팹

프리팹과 해당 프리팹이 사용하는 머터리얼들의 Addressable을 체크하여 어드레서블 애셋으로 등록

그룹 및 라벨로 에셋 분류 가능

  • 라벨은 하나의 에셋에 여러 개를 중복해 등록할 수 있지만 하나의 라벨만 등록하는 것을 권장함
  • 새로 그룹을 만들 시 동일하게 초기 세팅을 해준다

오디오 및 이미지 프리팹

원하는 오디오 파일 혹은 이미지 파일의 Addressable을 체크하여 어드레서블 에셋으로 등록 가능

스크립트로 프리팹 사용

선언 및 참조

스크립트에 AssetReference 형식으로 프리팹들을 참조할 수 있다.

  • AssetRefernceGameObject : 게임 오브젝트 형식
  • AssetReferenceSprite : 2D Sprite
  • AssetReferenceT<> : 제네릭 형식

이외에 다양한 형식이 존재한다.

이후 인스펙터 창에서 원하는 프리팹을 목록 중 선택, 그룹창, 폴더에서 드래그 등으로 넣는다.

사용

public class AddressableMananger : MonoBehaviour
{
    [SerializeField] private AssetReferenceGameObject cubeObj;
    [SerializeField] private AssetReferenceGameObject[] buildingObjs;

    [SerializeField] private AssetReferenceT<AudioClip> soundBGM;
    [SerializeField] private AssetReferenceSprite flagSprite;

	// 추후 Release해줄 때 사용하기 위한 오브젝트 레퍼런스 리스트
    private List<GameObject> _objects = new List<GameObject>();

    [SerializeField] private AudioSource bgmObj;
    [SerializeField] private Image flagImage;

    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(InitAddressable());
    }

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

    public void Button_SpawnObject()
    {
    	// 단일 게임오브젝트 생성
        cubeObj.InstantiateAsync().Completed += (obj) =>
        {
            _objects.Add(obj.Result);
        };

		// 다중 게임오브젝트 생성
        for (int i = 0; i < buildingObjs.Length; i++)
        {
            buildingObjs[i].InstantiateAsync().Completed += (obj) =>
            {
                _objects.Add(obj.Result);
            };
        }

        soundBGM.LoadAssetAsync().Completed += (clip) =>
        {
            bgmObj.clip = clip.Result;
            bgmObj.loop = true;
            bgmObj.Play();
        };

        flagSprite.LoadAssetAsync().Completed += (img) =>
        {
            flagImage.sprite = img.Result;
        };
    }
}

Build

Addressables Groups창에서 Build 0 New Build - Default Build Script 실행

빌드를 마친 후 Play Mode Script - Use Asset Database 선택

게임을 실행한 후 Button_SpawnObject() 함수 실행 시 미리 지정해둔 오브젝트를 실행할 수 있음

Release

위의 클래스에 Release함수를 추가해준다.

    public void Button_Release()
    {
        // LoadAssetAsync로 로드한 애셋을 Release해준다
        soundBGM.ReleaseAsset();
        flagSprite.ReleaseAsset();
        
        if (_objects.Count == 0)
            return;

        var index = _objects.Count - 1;

        for (int i = _objects.Count - 1; i > -1; i--)
        {
            // InstantiateAsync로 생성한 오브젝트들을 Release해준다
            Addressables.ReleaseInstance(_objects[i]);
            _objects.RemoveAt(i);
        }
    }

서버 사용

강의 영상

미리 로컬에서 위와 같이 에셋을 추가 및 설정한 후 서버를 적용할 수 있다

AWS S3 설정

1.AWS S3의 버킷을 만든다.

  • '모든 퍼블릭 액세스 차단' 체크 해제
  • '현재 설정으로 인해 이 버킷과 그 안에 포함된 객체가 퍼블릭 상태가 될 수 있음을 알고 있습니다.' 체크

2.버킷 페이지에 들어가 다음과 같이 설정해준다.

  • 권한 페이지에 진입
  • '버킷 정책 편집' 클릭
  • '버킷 ARN' 복사 후 '정책 생성기 버튼' 클릭
  • 'Select Type of Policy'를 'S3 Bucket Policy'로 선택
  • 'Principal'에는 *를 입력, 'Actions'에서는 'GetObject'선택
  • 'ARN'에는 아까 복사해둔 버킷 ARN 붙여넣기
  • 'Add Statement'버튼으로 규정 추가, 'Generate Policy'버튼 클릭
  • 팝업된 JSON구문을 복사해 정책 칸에 붙여넣기, "Resource"구문의 마지막에 /*추가
  • 변경 사항 저장 추가

3.아무 파일을 하나 업로드한 후 해당 파일의 객체 URL 주소를 복사한다.

Unity 설정

경로 설정

1.Remote Profile을 다음과 같이 설정한다.

  • 복사한 객체 URL주소를 Addressable ProfileRemote.LoadPath에 붙여넣은 후 파일 이름을 [BuildTarget]으로 변경해준다.

2.Addressable Group들의 Build & Load PathsRemote인지, Path Preview로 보이는 경로가 올바른지 확인한다. 시스템 세팅에서도 변경되었는지 확인한다.

3.AWS S3에 올린 파일을 삭제한다.

빌드 설정

1.Addressable GroupsBuild - Clear Build Cache - All을 선택해 이전 빌드 캐시 파일들을 삭제한다.

2.프로젝트의 파일 탐색기에서 Assets - ServerData내에 있는 Standalone...파일을 삭제한다.

3.Addressable GroupsBuild - New Build - Default Build Script로 재빌드

  1. 파일 탐색기의 Assets - ServerData내에 있는 Standalone...폴더를 AWS S3에 추가

  2. Addressable GroupsPlay Mode ScriptUse Existing Build로 선택해 서버에서 데이터를 받아오게 변경

서버에서 다운로드된 파일 위치

C:\Users\유저_이름\AppData\LocalLow\Unity\프로젝트_이름에 다운로드 된다.
테스트를 위해 다운로드 파일을 삭제하고 싶을 시 해당 폴더를 삭제하면 된다.

로딩 및 실행 스크립트

다운로드 매니저

어드레서블 에셋의 다운로드 필요 여부를 확인하고 만약 필요하면 다운로드를 실행하는 씬의 매니저

public class DownloadManager : MonoBehaviour
{
    [Header("UI")]
    public GameObject waitMessage;
    public GameObject downMessage;
    public Slider downSlider;
    public TMP_Text sizeText;
    public TMP_Text downValText;

    [Header("Label")] // 다운로드받을 애셋들의 라벨
    public AssetLabelReference defaultLabel;
    public AssetLabelReference matLabel;

    // 다운받을 애셋들의 파일 크기
    private long _patchSize;
    // 각각의 애셋들의 파일 크기
    private Dictionary<string, long> _patchMap = new Dictionary<string, long>();


    // Start is called before the first frame update
    void Start()
    {
        downMessage.SetActive(false);
        waitMessage.SetActive(true);
        
        StartCoroutine(InitAddressable());
        StartCoroutine(CheckUpdateFiles());
    }

    // 어드레서블을 초기화해주는 코루틴
    private IEnumerator InitAddressable()
    {
        var init = Addressables.InitializeAsync();
        yield return init;
    }

    // 업데이트할 파일을 체크하는 코루틴
    // 각 라벨별로 다운로드할 데이터를 파악한 후 추가 파일이 있으면 다운로드 버튼 활성화
    // 아니면 로딩 씬을 거쳐 샘플신을 로드하게끔 실행
    private IEnumerator CheckUpdateFiles()
    {
        var labels = new List<string>() { defaultLabel.labelString, matLabel.labelString };

        _patchSize = default;

        foreach (var label in labels)
        {
            var handle = Addressables.GetDownloadSizeAsync(label);
            yield return handle;
            _patchSize += handle.Result;

            if (_patchSize > Decimal.Zero)
            {
                waitMessage.SetActive(false);
                downMessage.SetActive(true);

                sizeText.text = "" + GetFileSize(_patchSize);
            }
            else
            {
                downValText.text = "100%";
                downSlider.value = 1f;
                yield return new WaitForSeconds(2f);
                LoadingManager.LoadScene("SampleScene");
            }
        }
    }

    public void Button_Download()
    {
        StartCoroutine(PatchFiles());
    }

    // 새 파일에 대한 패치를 실시하는 코루틴
    // 각 라벨에 대해 다운로드 여부를 파악한 후 다운로드할 파일이 있으면 다운로드 코루틴 시작
    // 직후 다운로드 상태 코루틴 시작
    private IEnumerator PatchFiles()
    {
        var labels = new List<string>() { defaultLabel.labelString, matLabel.labelString };

        _patchSize = default;

        foreach (var label in labels)
        {
            var handle = Addressables.GetDownloadSizeAsync(label);
            yield return handle;

            if (handle.Result != Decimal.Zero)
            {
                StartCoroutine(DownLoadFromLabel(label));
            }
        }

        yield return CheckDownloadStatus();
    }

    // 각각의 라벨에 대해 다운로드하게끔 하는 코루틴
    // 해당 라벨에 대해 다운로드 데이터를 받아온 후 핸들을 Release
    private IEnumerator DownLoadFromLabel(string label)
    {
        _patchMap.Add(label, 0);

        var handle = Addressables.DownloadDependenciesAsync(label);

        while (!handle.IsDone)
        {
            _patchMap[label] = handle.GetDownloadStatus().DownloadedBytes;
            yield return new WaitForEndOfFrame();
        }

        _patchMap[label] = handle.GetDownloadStatus().TotalBytes;
        Addressables.Release(handle);
    }

    // 다운로드 현황을 체크하는 코루틴
    // 로딩바와 진행도를 갱신해주고 완료시 로딩신 후 샘플신으로 넘겨주기
    private IEnumerator CheckDownloadStatus()
    {
        var total = 0f;
        downValText.text = "0 %";

        while (true)
        {
            total += _patchMap.Sum(tmp => tmp.Value);

            var perValue = total / _patchSize;
            downSlider.value = perValue;
            downValText.text = string.Format("{0:##.##}", perValue * 100) + " %";

            if (total.Equals(_patchSize))
            {
                LoadingManager.LoadScene("SampleScene");
                break;
            }

            total = 0f;
            yield return new WaitForEndOfFrame();
        }
    }

    // 파일 사이즈에 따라 용량에 대한 
    public string GetFileSize(long byteCnt)
    {
        string size = "0 Bytes";

        if (byteCnt >= 1073741824.0)
        {
            size = string.Format("{0:##.##}", byteCnt / 1073741824.0) + " GB";
        }
        else if (byteCnt >= 1048576.0)
        {
            size = string.Format("{0:##.##}", byteCnt / 1048576.0) + " MB";
        }
        else if (byteCnt >= 1024.0)
        {
            size = string.Format("{0:##.##}", byteCnt / 1024.0) + " KB";
        }
        else if (byteCnt > 0 && byteCnt < 1024.0)
        {
            size = string.Format("{0:##.##}", byteCnt) + " KB";
        }
        
        return size;
    }
}

로딩 매니저

한 씬에서 다른 씬으로 로딩할 때 중간에 거쳐가는 신 매니저

public class LoadingManager : MonoBehaviour
{
    // 로딩할 신의 이름
    public static string nextScene;

    public Slider loadingBar;
    
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(StartLoading());
    }

    // 지정된 신으로 로딩을 시작하는 코루틴
    private IEnumerator StartLoading()
    {
        yield return null;

        AsyncOperation op = SceneManager.LoadSceneAsync(nextScene);
        op.allowSceneActivation = false;

        float timer = 0f;

        while (!op.isDone)
        {
            yield return null;

            timer += Time.deltaTime;

            if (op.progress < 0.9f)
            {
                loadingBar.value = Mathf.Lerp(loadingBar.value, op.progress, timer);
                if (loadingBar.value >= op.progress)
                {
                    timer = 0f;
                }
            }
            else
            {
                loadingBar.value = Mathf.Lerp(loadingBar.value, 1f, timer);
                
                if (loadingBar.value.Equals(1f))
                {
                    yield return new WaitForSeconds(2f);
                    op.allowSceneActivation = true;
                    yield break;
                }
            }
        }
    }

    // 한 씬에서 다른 씬으로 넘길 때 사용하는 씬 로딩 함수
    public static void LoadScene(string sceneName)
    {
        nextScene = sceneName;
        SceneManager.LoadScene("LoadingScene");
    }
}

애셋 변경 후 서버에 패치하기

  1. 에셋 파일 변경(파일의 이름은 그대로 하는 것을 권장) 후 Addressables Groups 에서Build - Update a Previous Build를 선택해 빌드

    • 이후에 파일 탐색기가 팝업되는데 이 때 '빌드하려는 목표 OS 폴더'안에 있는 .bin파일을 선택해 빌드
  2. 프로젝트 파일의 ServerData에 들어가서 파일의 수정 날짜 및 시간이 빌드한 날짜인 파일들을 선택.
    이들이 새로 변경된 파일들이다.

  3. AWS S3에 진입해 .json, .hash, 에셋을 변경한 로컬 그룹의 Bundle파일을 삭제한 후 변경된 파일을 업로드한다.

profile
학습한 내용을 빠르게 다시 찾기 위한 저장소

0개의 댓글