유니티 Addressable (사용법,서버에서 받기)

2022. 7. 26. 14:03유니티 unity

유니티 Addressable에 대해서 알아보겠습니다.

콘텐츠를 빌드하고 로드하는 시스템입니다.

기존에는 리소스 폴더를 가 있는데

빌드 사이즈가 커지는 문제와 앱 로딩 시간이 길어지는 문제 때문에

에셋번들이 생겼는데

에셋번들 하드코딩을 해야 하는 단점과 어렵다는 단점이 있어서 그것을 보완하기 위해서

어드레서블이 생겼습니다. 

에셋번들을 좀 더 쉽게 사용하기 위해서 만들어진 유틸이라고 생각하시면 됩니다.

유니티 버전 2018.2 이상부터 사용이 가능

 

모든 동작이 비동기로 구현하기 때문에 잘 생각하시면서 사용해야 합니다.

 

 

설치

Package manager에서 설치

 

실행

 

 

 

 

이렇게 사용하면 끝이 납니다.

 

플레이 모드 설정하는 칸인데

 

Use Asset Database 랑 Simulate Groups는 에셋 번들을 만들지 않았어도 에셋번들을 만든 것처럼 작동하는 거고

여기서 문제가 없어도 실제 런타임에서 문제가 생길 수도 있습니다

 

Use Existing Build는 실제로 빌드하고 작동하는 것처럼 나오기 때문에

실제 빌드와 동일한 환경에서 테스트하고 싶을 때는 이것을 체크합니다.

 

어드레서블을 확인하기 위해서 게임 오브젝트를 만들겠습니다

 

5개 오브젝트를 만들어서

 

폴더로 넣어서 프리팹화 시킵니다.

 

 

addressable에 체크해서 어드레서블에 올립니다. 옆에 주소같이 써있는거는 말 그대로 어드레서블의 주소 이름이라고 생각하시면 편합니다.

 

5개 전부 이렇게 수정했습니다.

어드레서블 그룹을 보시면 체크한 오브젝트들이 포함되어있습니다.

 

Labels을 만들어서 같은 그룹에 포함시킬 수 있습니다.

 

 

 

 

 

 

이제 게임에서 불러오는 방법에 대해서 알아보겠습니다.

 

2개의 버튼을 만들었습니다.

스크립트 하나를 만듭니다.

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class Addressable_Test1 : MonoBehaviour
{
    public AssetReference aref;

    public void Btn1()
    //변수로 가져와서 불러오기
    {
        Addressables.LoadAssetAsync<GameObject>(aref).Completed += (op) =>
        {
            if (op.Status != AsyncOperationStatus.Succeeded)
            {
                return;
            }

            Instantiate(op.Result, new Vector3(0, 1, 0), Quaternion.identity);
        };
    }

    public void Btn2()
    //직접 주소를 넣기
    {
        Addressables.LoadAssetAsync<GameObject>("Test/Black").Completed += (op) =>
        {
            if (op.Status != AsyncOperationStatus.Succeeded)
            {
                return;
            }

            Instantiate(op.Result, new Vector3(0, 1, 0), Quaternion.identity);

        };
    }
}

 

 

변수로 쓰게 되면 따로 넣어줘야 합니다

LoadAssetAsync로 어드레서블을 비동기로 불러와서

op.Result로 결괏값을 가져옵니다 

 

메모리상으로 올라왔으니 이걸 내리고 싶다면 

Addressables.Release(op);

로 내릴 수 있습니다.

 

InstantiateAsync이라는 함수를 이용하면 게임 오브젝트를 바로 생성시킬 수 있습니다.

 

public void Btn1()
//변수로 가져와서 불러오기
{
    // Addressables.LoadAssetAsync<GameObject>(aref).Completed += (op) =>
    // {
    //     if (op.Status != AsyncOperationStatus.Succeeded)
    //     {
    //         return;
    //     }
    //
    //     Instantiate(op.Result, new Vector3(0, 1, 0), Quaternion.identity);
    // };

    aref.InstantiateAsync(new Vector3(0, 1, 0), Quaternion.identity);
}

 

어드레서블을 이용하면 원하는 거만 메모리에 올리고

이제 사용이 필요가 없다면 메모리를 해제해서 매우 유용하게 사용할 수 있습니다.

 

그리고 번들을 로컬이 아닌 서버에서 다운로드하여서 사용이 가능합니다.

이것을 활용한다면 서버에 있는 번들만 바꿔주면 되니까 빌드 없이 번들 안에 있는 내용들을 바꿀 수 있습니다.

서버에 리소스들을 넣어서 용량을 매우 크게 감소를 할 수 도 있고

빌드 없이도 패치를 할 수 있다는 매우 큰 장점이 있습니다.

 

 

예시 롤토체스

 

이런 식으로 빌드 없이도 추가 패치를 할 수 있습니다.

 

이번에는 예시로

게임 시작 버튼을 눌러서 다운로드해야 할 용량을 보여주고 다운로드를 하게 되면

로딩바 % 를 보여줘서 현재 상황을 알려주는 간단한 구현과

빌드 없이 번들만 바꿔서 패치를 하는 방법을 보여드리겠습니다.

 

 

 

 

음악 5개를 추가해서 넣었습니다 라벨을 Test로 하나로 통일했습니다.

무료 사이트 https://www.bensound.com/free-music-for-videos

 

해당 번들을 가지고 있는지 없는지 확인을 하려면 

GetDownloadSizeAsync로 확인 가능하고

파일 하나씩도 가능하고 Lable로 묶어서 여러 개로 한 번에 확인이 가능합니다.

파일이 없다면 DownloadDependenciesAsync를 이용해서 다운로드합니다

만약 파일이 있는 상태에서 DownloadDependenciesAsync를 사용하면 추가로 다운로드하지 않고 넘어갑니다.

번들 하나가 수정이 된다면 전체 다시 다운로드가 아니라 수정된 번들만 따로 받습니다.

 

라벨 또한 변수로 가져올 수 있습니다.

public AssetLabelReference 변수명;

이것을 활용해서 스크립트를 하나 만들겠습니다.

 

using System.Collections;
using TMPro;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Random = UnityEngine.Random;

public class Addressable_Test1 : MonoBehaviour
{
    public AssetReference[] obs; // 게임오브젝트들
    public AssetReference[] musics; // 음악

    public AudioSource audioSource; // 오디오소스
    
    public GameObject StartPanel; //UI
    public GameObject DownPanel; //UI
    public GameObject LoadingPanel; // UI
    
    private AsyncOperationHandle handle;


    public TextMeshProUGUI byteText; //다운로드 사이즈
    public Slider slider;  // 로딩%
    public TextMeshProUGUI perText; //퍼센트 Text 
    

    public void StartBtn()
    //시작버튼 누름
    {
        StartCoroutine(StartFunc());
        StartPanel.SetActive(false);
    }

    IEnumerator StartFunc()
    {
        AsyncOperationHandle<long> getDownloadSize = Addressables.GetDownloadSizeAsync("Test");
        //Test 이라는 라벨 다운로드사이즈 확인
        yield return getDownloadSize;
        
        if (getDownloadSize.Result > 0)
            //다운로드 사이즈가 0 이상이라면 다운받아야하기 때문에 다운로드UI 표시
        {
            
            DownPanel.SetActive(true);
            byteText.text=$"{getDownloadSize.Result} Byte";
        }
        else
        {
            //아니라면 다음으로 넘어간다.
            NextShow();
        }
        //메모리 내림
        Addressables.Release(getDownloadSize);
    }

    public void DownBtn()
    //다운로드 시작 버튼 누름
    {
        
        StartCoroutine(DownFunc());
        DownPanel.SetActive(false);
    }

    IEnumerator DownFunc()
    {
        
        handle = Addressables.DownloadDependenciesAsync("Test");
        //Test 라벨을 다운로드 받습니다.

        StartCoroutine(Show());
        //로딩바 보여줌
        yield return handle;
        yield return new WaitForSeconds(1);
        //1초대기
        
        LoadingPanel.SetActive(false);
        NextShow();
        //다음으로 넘어감
        
        //메모리 해제
        Addressables.Release(handle);
    }


    public void NextShow()
    {
        Addressables.InstantiateAsync(obs[0], new Vector3(-5, 0, 0), quaternion.identity);
        Addressables.InstantiateAsync(obs[1], new Vector3(-2.5f, 0, 0), quaternion.identity);
        Addressables.InstantiateAsync(obs[2], new Vector3(0, 0, 0), quaternion.identity);
        Addressables.InstantiateAsync(obs[3], new Vector3(2.5f, 0, 0), quaternion.identity);
        Addressables.InstantiateAsync(obs[4], new Vector3(5, 0, 0), quaternion.identity);
        //오브젝트 소환


        int ran = Random.Range(0,musics.Length);
        //랜덤으로 뮤직 하나 선택

        Addressables.LoadAssetAsync<AudioClip>(musics[ran]).Completed += (op) =>
        {
            if (op.Status != AsyncOperationStatus.Succeeded)
            {
                return;
            }

            audioSource.clip = op.Result;
            audioSource.Play();
            //뮤직 스타트
        };
    }

    IEnumerator Show()
    //다운로드 % 보여주는 함수
    {
        LoadingPanel.SetActive(true);
        yield return new WaitUntil(() => handle.IsValid());
        while (handle.PercentComplete<1)
        {
            slider.value = handle.PercentComplete;
            perText.text=$"{handle.PercentComplete*100:F2}%";
            yield return null;
        }
        slider.value = 1;
        perText.text = "100%";
    }
    
}

 

 

 

 

 

 

이제 번들을 다운로드할 주소만 따로 넣는다면 끝입니다.

 

서버를 선택해야 하는데

로컬 서버를 하시 거나

아마존

파이어 베이스

구글 클라우드

등등

여러 가지 있습니다

해당 다운로드할 링크만 있다면 어떤 것도 다 가능합니다.

 

여기서 파이어 베이스는

주소가 https://가 아닌 gs:// 로 시작해서 따로 세팅을 해줘야 합니다

https://gitlab.com/robinbird-studios/libraries/unity-plugins/firebase-tools/-/tree/master/Storage/Addressables

 

저는 무료인 구름 ide를 사용하겠습니다.

예전에 어드레서블을 사용할 때는 파이어 베이스를 사용을 했었는데 

 

이번에는 구름 ide가 최근에 무료 플랜도 항상 켜 두기를 사용이 가능해서 사용하기 편하더라고요 가입절차도 복잡하지 않습니다 

 

다른 클라우드 사용하시는 분들은 번들을 넣고 링크만 있으면 됩니다.

 

 

구름ide

https://ide.goorm.io

 

 

 

항상 켜 두기를 사용하지 않으면 일정 시간이 지나면 자동으로 꺼집니다.

 

구름 ide를 켜놓고 다시 유니티로 돌아갑니다 서버에서 다운받을려면 몇 가지 설정을 해야 합니다.

 

 

전 에셋 번들을 테스트하기 위해서 설정했습니다.

 

 

 

이름을 Remote로 설정합니다 원하는 이름으로

Custom으로 설정해야 하고

Remote.LoadPath를 서버링크를 넣으시면 됩니다.

 

[BuildTarget]라는 게 나오는데

PC는 StandalonWindows64 폴더

안드로이드는 Android라는 폴더로 설정됩니다.

 

임시로 http://서버주소/[BuildTarget] 로 설정합니다

 

 

 

 

 

 

Path Preview를 보시면

/StandaloneWindows64 가 따로 넣어졌습니다.

 

 

 

이제 설정이 끝났으면 다시 서버로 돌아가서 링크를  복사합니다.

 

구름ide로 가서 폴더를 만듭니다.

 

 

전 CDN 폴더 안에 넣었습니다.

 

 

 

주소를 복사하고 뒤에 컨테이너 이름과 폴더를 넣습니다

 

전 컨테이너 이름은 addressable 이니까

https://addressable-ofpne.run.goorm.io/addressable/CDN 입니다.

 

 

마지막으로 확인해봅니다

 

 

이제 빌드를 해봅시다

 

기존 빌드 설치 폴더를 안 건드셨다면 본인 프로젝트 폴더에 가셔서 ServerData라는 폴더를 찾습니다

 

 

 

 

이 파일들을 이제 서버에 올리면 됩니다.

 

 

구름 ide는 따로 서버를 켜줘야 하는데

 

service apache2 start를 쳐줍시다

 

한번 주소창에 실행해서 서버가 켜졌나 확인해봅니다

 

 

이제 빌드를 하고 실행을 해보면 잘 나옵니다.

 

 

 

 

 

C:\Users\사용자이름\AppData\LocalLow\Unity

 

잘 들어와 있습니다.

 

파일을 지우고 싶으면 폴더를 지우거나

 

에디터상에서 

Caching.ClearCache();

를 실행하면 됩니다.

 

이번엔 번들의 파일을 수정하고 빌드 없이 실행해보겠습니다.

폴더를 삭제하고

 

테스트를 위해서 에셋스토어에서 아무거나 다운받으세요

https://assetstore.unity.com/packages/3d/characters/humanoids/zombie-30232

 

 

 

 

 

 

빌드 없이 패치가 되었습니다.

 

Player Version Override 에다가 이름을 적으면 빌드시

 

이런 식으로 나옵니다.

 

 

 

 

 

 

어드레서블을 활용해서 앱 용량을 크게 줄일 수 있습니다.

씬도 어드레서블로 저장이 가능하니까 활용을 잘하면 정말 괜찮은 기능인거 같습니다.

 

그리고 이번에 서버를 구름 ide를 써봤는데

구름ide 항상 켜 두기가 무료 플랜에서도 사용이 가능해져서 좋네요 개인프로젝트나 테스트용도로는 정말 좋은 선택이라고 생각이 듭니다.

 

깃허브주소

https://github.com/wolstar415/unity_Addressables

 

스크립트보기

더보기
using System;
using System.Collections;
using TMPro;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Random = UnityEngine.Random;

public class Addressable_Test1 : MonoBehaviour
{
    public AssetReference[] obs; // 게임오브젝트들
    public AssetReference[] musics; // 음악

    public AudioSource audioSource; // 오디오소스

    public GameObject StartPanel; //UI
    public GameObject DownPanel; //UI
    public GameObject LoadingPanel; // UI

    private AsyncOperationHandle handle;


    public TextMeshProUGUI byteText; //다운로드 사이즈
    public Slider slider; // 로딩%
    public TextMeshProUGUI perText; //퍼센트 Text 

    // private void Start()
    // {
    //     Caching.ClearCache();
    // }

    public void StartBtn()
        //시작버튼 누름
    {
        StartCoroutine(StartFunc());
        StartPanel.SetActive(false);
    }

    IEnumerator StartFunc()
    {
        AsyncOperationHandle<long> getDownloadSize = Addressables.GetDownloadSizeAsync("Test");
        //Test 이라는 라벨 다운로드사이즈 확인
        yield return getDownloadSize;

        if (getDownloadSize.Result > 0)
            //다운로드 사이즈가 0 이상이라면 다운받아야하기 때문에 다운로드UI 표시
        {
            DownPanel.SetActive(true);
            byteText.text = $"{getDownloadSize.Result} Byte";
        }
        else
        {
            //아니라면 다음으로 넘어간다.
            NextShow();
        }

        //메모리 내림
        Addressables.Release(getDownloadSize);
    }

    public void DownBtn()
        //다운로드 시작 버튼 누름
    {
        StartCoroutine(DownFunc());
        DownPanel.SetActive(false);
    }

    IEnumerator DownFunc()
    {
        handle = Addressables.DownloadDependenciesAsync("Test");
        //Test 라벨을 다운로드 받습니다.

        StartCoroutine(Show());
        //로딩바 보여줌
        yield return handle;
        yield return new WaitForSeconds(1);
        //1초대기

        LoadingPanel.SetActive(false);
        NextShow();
        //다음으로 넘어감

        //메모리 해제
        Addressables.Release(handle);
    }


    public void NextShow()
    {
        Addressables.InstantiateAsync(obs[0], new Vector3(-5, 0, 0), quaternion.identity);
        Addressables.InstantiateAsync(obs[1], new Vector3(-2.5f, 0, 0), quaternion.identity);
        Addressables.InstantiateAsync(obs[2], new Vector3(0, 0, 0), quaternion.identity);
        Addressables.InstantiateAsync(obs[3], new Vector3(2.5f, 0, 0), quaternion.identity);
        Addressables.InstantiateAsync(obs[4], new Vector3(5, 0, 0), quaternion.identity);
        //오브젝트 소환


        int ran = Random.Range(0, musics.Length);
        //랜덤으로 뮤직 하나 선택

        Addressables.LoadAssetAsync<AudioClip>(musics[ran]).Completed += (op) =>
        {
            if (op.Status != AsyncOperationStatus.Succeeded)
            {
                return;
            }

            audioSource.clip = op.Result;
            audioSource.Play();
            //뮤직 스타트
        };
    }

    IEnumerator Show()
        //다운로드 % 보여주는 함수
    {
        LoadingPanel.SetActive(true);
        yield return new WaitUntil(() => handle.IsValid());
        while (handle.PercentComplete < 1)
        {
            slider.value = handle.PercentComplete;
            perText.text = $"{handle.PercentComplete * 100:F2}%";
            yield return null;
        }

        slider.value = 1;
        perText.text = "100%";
    }
}