Resources, Asset Bundle, Addressable

최정훈·2024년 9월 16일

유니티 내 에셋 런타임 로드는 Resources폴더에서 Asset Bundle, Addressable방식으로 발전하고 있다.

Resources

Resource 폴더에서 사용한 Asset들을 관리해주는 방식이다.

// 기본적인 형태
var resoureToLoad = Resource.Load("Path");

// 경로지정
var resoureToLoad = Resource.Load<Sprite>("Sprite/001");

장점

  • 직관적이고 사용이 쉽다

단점

  • 실제로 사용하기 않는 에셋도 같이 빌드될 수 있다.
  • 빌드 시 폴더 내부에 있는 모든 것이 같이 빌드되기 때문에, 빌드 사이즈가 커진다
  • 에셋의 이름을 통해서 로드하기 때문에, 이름을 변경할 때 신중하여야 한다.
  • 변경사항이 생겼을 때, 통째로 다시 빌드해야한다.

Asset Bundle

에셋 번들(AssetBundle) 은 플랫폼 특정 에셋(모델, 텍스처, 프리팹, 오디오 클립 , 씬 전체)이 들어있는 아카이브 파일로 실행 시에 로드할 수 있습니다.

-유니티 메뉴얼 발췌-

이름에서도 알 수 있듯이 에셋번들은 asset(에셋)bundle(묶음)이 합쳐진 단어로, 에셋들의 묶음이다.

에셋번들에 대한 이해를 돕기 위해서 예시를 들어보자. 플레이 스토어에서 모바일 게임을 다운받는다고 가정하자. 분명 게임 자체는 빠르게 다운되지만, 인게임 내에서 리소스를 오랜 시간동안 다운받는 경우가 대부분이다. 우선 추가적으로 다운로드받는 리소스가 에셋번들이라고 생각하면 편리하다.

에셋번들을 사용하는 이유

그렇다면 에셋번들을 사용하는 이유가 뭘까? 게임을 만들다보면 용량이 점점 커질 것이다. 자연스럽게 완성했을 때의 앱의 용량도 증가할 것이고, 다운받는 시간도 길어질 것이다. 에셋번들을 사용하면 이를 완화할 수 있다.

에셋번들을 사용하면, 에셋들을 런타임에 불러서 사용할 수 있는데, 적은 용량으로 게임을 설치한 후 실행하는 시점에 필요한 데이터를 추가적으로 불러오는 방식이다. 이는 주로 초기의 앱 용량을 줄이거나, 업데이트 및 패치, DLC(추가 컨텐츠) 다운로드 등에 사용된다.

에셋번들 사용법

첫번째 사진은 내가 현재 만드는 게임에 사용될 예정인 Fox아바타의 프리펩이다. 이렇게 하단에 보면 AssetBundle None이라는 레이블을 확인할 수 있다. 기본적으로 에셋들의 에셋번들 설정은 None이다. 에셋번들에 속하지 않고, 프로젝트 자체에 패키지화 되는 것이다. 새로운 에셋번들을 생성하기 위해서는 New를 클릭하고 다음과 같이 번들의 이름을 입력한다. /를 사용해서 하위 구성요소로 구별할 수도 있다.

이렇게 만들어진 에셋번들을 게임에서 사용하기 위해서는 에셋번들을 따로 빌드해야한다.

using System.IO;
using UnityEditor;

public class AssetBundleBuild 
{
    [MenuItem("Assets/Build AssetBundles")]
    public static void BundleBuild()
    {
        string dir = "Assets/AssetBundles";

        if (!Directory.Exists(dir))
        {
            Directory.CreateDirectory(dir);
        }
        
        BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
    }
}

위와 같이 코드를 작성하여 Asset매뉴 하위로 Build AssetBunldes라는 메뉴를 하나 생성하여 이를 누르면 설정한 경로로 에셋번들이 빌드되도록 설계했다.(에디터API를 사용해야하기 때문에, Editor라는 폴더에 이 스크립트가 위치해야 함에 주의하자)

Build AssetBunldes이라는 메뉴를 누르면 다음과 같이 지정한 dir의 경로에 에셋번들이 생성된 것을 확인할 수 있다.

이를 실제로 사용하기 위해서는, 이 빌드한 프로젝트 파일을 로드하는 과정이 있어야한다.

using System.Collections;
using UnityEngine;

public class AssetMgr : MonoBehaviour
{
    IEnumerator Start()
    {
        string path = "Assets/AssetBundles/fox";
        AssetBundle asset = AssetBundle.LoadFromFile(path);
        
        if (asset == null)
        {
            yield break;
        }
        
        var fox = asset.LoadAsset<GameObject>("The Fox");
        if (fox == null)
        {
            Debug.LogError("Failed to load 'fox' GameObject from AssetBundle!");
            yield break;
        }
        
        var player = Instantiate(fox);
        player.transform.position = new Vector3(0, 0, 0);
        
        // player 언로드
        yield return new WaitUntill(() => player.isDead());
        asset.Unload();
        Destroy(player);
    }
}

위의 코드를 통해서 에셋번들을 저장한 폴더에 접근해서 원하는 객체를 생성할 수 있다.

실제로 이렇게 에셋번들을 통해 빌드를 진행하면, 사용하지 않았을 때보다 실행파일의 용량이 줄어든 것을 확인할 수 있다.

유니티 에디터에서 위와 동일한 방법으로 세팅한 후 실행시키면 원하는 결과대로 동작하는 것을 확인할 수 있다. 하지만, 이를 빌드시켜 실행해보면 아무것도 생성되지 않는 것을 확인할 수 있는데, 이는 저장한 에셋번들을 다운하는 과정이 없었기 때문이다. 그렇기 때문에 에셋번들을 사용하기 위해서는 로드하는 과정과 로드했던 정보를 다시 다운로드하는 과정이 모두 필요하다.

추가적으로 이렇게 에셋번들을 통해서 플레이어를 생성한다고 해서 끝이 아니다. 위의 과정은 메모리에 할당되어 등록된다. 추후에 게임오버와 같은 상황으로 인해서 더 이상 플레이어를 사용할 필요가 없을 때는 이를 수동으로 메모리에서 해제해야한다는 번거로움이 존재한다.

장점

  • 필요한 것들만 묶음으로 저장하기 때문에, 리소스 폴더보다 효율적이다.
  • 빌드 용량이 작아진다.
  • 변경사항이 생기더라도, 에셋번들만 다시 빌드하면 된다.

단점

  • 개발자가 저장, 로드, 다운까지 모든 것을 설계해야 한다(하드코딩).
  • 종속성에 문제가 있다.
    • 만약 2개의 번들에서 동일한 스프라이트를 가지고 있다고 가정하면, 이들을 공유하지 못하고 각자 별개로 가지고있는다.

Addressable

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

-유니티 메뉴얼 발췌-

기존의 AssetBundle의 단점들을 일정부분 완화해준다. AssetBundle은 에셋의 경로를 통해서 직접적으로 가져오는 반면에, 어드레서블은 address라는 중재자를 통해서 에셋을 가져온다. 이 중재자가 번거로운 일들을 처리해준다.

에셋에 주소를 지정하는 방식으로, 주소(address)로 쉽게 에셋을 로드할 수 있다. 에셋을 로드하는 쪽에서는 로드하고자 하는 에셋의 주소만 알고 있으면 되기 때문에, 에셋의 위치가 변경되어도 상관없이 사용가능하다.

이를 사용하기 위해서는 유니티 Package Manager에서 Addressable을 설치해야한다.

설치 후, 에셋을 눌러보면 Addressable토글이 생긴것을 확인할 수 있다. 이 토글을 활성화하면. 주소를 입력할 수 있는데, 디폴트 값으로 현재 에셋의 경로가 입력되어 있다.

아래와 같은 방식으로 에셋을 바로 게임오브젝트로 생성이 가능하다

using UnityEngine;
using UnityEngine.AddressableAssets; // using 선언

public class AddressableTest : MonoBehaviour
{
    [SerializeField] private AssetReference assetReference;

    void Start()
    {
		// AssetReference를 통한 방법
        Addressables.InstantiateAsync(assetReference);
        
        // 어드레스를 통한 방법
        Addressables.InstantiateAsync("Assets/Fox/Addressable Fox.prefab");
    }
}

어드레서블 함수들에서 알 수 있듯이, 어드레서블은 비동기로 되어있다. 따라서, 어드레서블을 다룰 때는, 비동기적으로 다루어야한다.

profile
게임개발자(희망)의 공부일지

0개의 댓글