Unity

[Unity] AssetBundle을 활용해 Scene 로드하기

JiHxxn 2024. 7. 9. 22:38

🤔 사용 목적

프로젝트에서 다른 프로젝트의 Scene을 사용해야 할 때, 해당 Scene을 통째로 번들화하여 로드해 사용할 수 있다.

아래 가이드대로 따라하게 되면 게임 시작 버튼 클릭 시, 해당 게임의 에셋 번들을 로드하여 게임을 실행시킬 수 있게 된다.


1. AssetBundle 추출 및 셋팅

1. 번들 화 될 오브젝트가 있는 프로젝트에서 Asset Bundle Browser 패키지

- 설치 Git URL : com.unity.assetbundlebrowser

 

2. Asset Bundle Browser → Configure 탭에서 번들화 할 오브젝트 추가

- Build 되는 Scene들을 드래그해주면 의존되는 파일들도 자동으로 추가가 된다.

 

3. Asset Bundle Browser → Build 탭에서 번들화

- B ulid Target, Clear Folders, Compression(LZ4) 확인

 

4. 번들화 한 AssetBundle을 사용할 프로젝트에 옮기기

Android.manifest 파일의 경우, 프로젝트 내에 한 개만 있으면 충분(플랫폼당 하나)

 

5. 게임의 스크립트 파일들을 사용할 프로젝트에 옮기기


2. AssetBundle 로드

플랫폼에 따라 StreamingAssets → AssetBundles → ANDROID/IOS 폴더에 에셋번들 넣기

💱 Coroutine 버전

  • 버튼 클릭 시 작동되는 함수
public void OnClickGameButton()
{
    // LoadGame(게임이름, 게임의 메인 씬 이름)
    LoadBundleSceneManager.Instance.LoadGame("multiplicationtable", "MainScene");
}
  • 게임 로드
private string gameAssetBundleName;
private string startSceneName;

private bool isCoroutineRunning;

public void LoadGame(string _assetBundelName, string startSceneName)
{
    isCoroutineRunning = false;

    // 전역변수에 번들이름 담아놓기
    gameAssetBundleName = _assetBundelName;
    this.startSceneName = startSceneName;

    // Empty Scene Load
    StopAllCoroutines();
    StartCoroutine(LoadEmptyScene(StartLoadingGame));
}
// Scene이동 시 EmptyScene을 거쳐간다.
// 중복 실행을 방지하고, UI를 활성화하고 로딩 패널을 연다.
// EmptyScene 로드가 완료되면, _onEmptySceneLoaded 콜백을 호출한다.
IEnumerator LoadEmptyScene(Action<AsyncOperation> _onEmptySceneLoaded)
{
    // 중복 실행 방지
    if (isCoroutineRunning)
    {
        yield break;
    }
    isCoroutineRunning = true;

    //로딩 Panel
    UIManager.Instance.OpenGameUIPanel(EUIPanelName.Loding);

    AsyncOperation asyncOperation = SceneManager.LoadSceneAsync("00_EmptyScene");
    asyncOperation.allowSceneActivation = false;
    asyncOperation.completed += _onEmptySceneLoaded;

    while (asyncOperation.allowSceneActivation == false)
    {
        if (asyncOperation.progress >= 0.9f && asyncOperation.allowSceneActivation == false)
        {
            asyncOperation.allowSceneActivation = true;
        }

        yield return null;
    }
}
// EmptyScene 로드가 완료된 후 호출되는 함수.
// 에셋 번들을 로드하고, 해당 번들에 포함된 씬을 찾아서 로드한다.
void StartLoadingGame(AsyncOperation asyncOperation)
{
    isCoroutineRunning = false;

    // AssetBundle 경로 확인
    string assetBundleName = gameAssetBundleName.ToLower();

    string path = Path.Combine(Application.streamingAssetsPath, "AssetBundles/ANDROID", assetBundleName);

    // AssetBundle 로드
    currentAssetBundle = AssetBundle.LoadFromFile(path);

    //방어코드
    if (currentAssetBundle == null)
    {
        Debug.Log($"{this.name}::: {currentAssetBundle} = SceneBundle 없음");
    }
    else
    {
        string[] sceneNameArray = currentAssetBundle.GetAllScenePaths();

        foreach (string sceneName in sceneNameArray)
        {
            if (sceneName.Contains(startSceneName))
            {
		            // 에셋 번들을 로드하는 코루틴 실행
                StartCoroutine(StartLoadingGameRoutine(sceneName));
                break;
            }
        }
    }
}
// 실제 씬을 로드하는 코루틴, 로딩 진행도를 UI에 업데이트하고, 로딩이 완료되면 씬을 활성화
IEnumerator StartLoadingGameRoutine(string sceneName)
{
    AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneName);
    asyncOperation.allowSceneActivation = false;

    while (!asyncOperation.isDone)
    {
        //로딩바
        UIManager.Instance.SetSliderValue(EUIPanelName.Loding, asyncOperation.progress);

        if (asyncOperation.progress >= 0.9f && asyncOperation.allowSceneActivation == false)
        {
            asyncOperation.allowSceneActivation = true;
        }

        yield return null;
    }
}

🔁 Unitask 버전

[SerializeField] private Image loadingBar;

public async UniTask LoadAppGameAsync()
{
    string assetBundleName = "multiplicationtable";
    AssetBundle assetBundle;

    string platform = "Android";
    string type = "Games";
        
    //에셋번들 경로 지정
    string path = Path.Combine(Application.streamingAssetsPath, platform, type, assetBundleName.ToLower());

    assetBundle = AssetBundle.LoadFromFile(path);

    if (assetBundle == null)
    {
        await UniTask.Yield();
        return;
    }

    // 에셋번들 내의 메인 씬을 불러오는 부분
    AsyncOperation asyncOperation = SceneManager.LoadSceneAsync("MainScene");
    while (!asyncOperation.isDone)
    {
        await UniTask.Yield();
        // 로딩바
        loadingBar.fillAmount = asyncOperation.progress;
    }

    asyncOperation.allowSceneActivation = true;

    await UniTask.Delay(100);

    asyncOperation.allowSceneActivation = true;
} 

public async void OnClickLoadAssetBundle()
{
    await LoadAppGameAsync();
}

⤵️ 게임 종료 시 메모리 해제

  • 언로딩 (LoadBundleSceneManager.cs)
    • 게임 안에서 UnloadGame()을 통하여 다시 MainScene으로 돌아가게 할 수 있다.
    • 해당 미니게임 종료 시 반드시 번들을 메모리 해주고, 해당 미니게임에 사용되었던 싱글톤 매니저도 삭제해 준다.
public void UnloadGame()
{
    UnloadUnusedAssetBundle();
    LoadMainScene();
    DestroyManager();
}

public void UnloadUnusedAssetBundle()
{
    if (currentAssetBundle != null)
    {
        currentAssetBundle.Unload(true);
        currentAssetBundle = null;
    }
}

// 메인 씬을 로드하는 메소드. 빈 씬을 로드하고, 로드가 완료되면 메인 씬 로드
public void LoadMainScene()
{
    // Empty Scene Load
    StopAllCoroutines();
    StartCoroutine(LoadEmptyScene(StartLoadingMain));
}

// 빈 씬 로드 완료 후 메인 씬 로드 시작
void StartLoadingMain(AsyncOperation asyncOperation)
{
    StopAllCoroutines();
    StartCoroutine(LoadMainRoutine("01_MainScene"));
}

// 메인 씬 로드하는 코루틴, 로딩 진행도 UI에 업데이트하고, 로딩 완료되면 씬 활성화
IEnumerator LoadMainRoutine(string _mainScene)
{
    AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(_mainScene);
    asyncOperation.allowSceneActivation = false;

    while (!asyncOperation.isDone)
    {
        //로딩바
        //UIManager.Instance.SetSliderValue(EUIPanelName.Loding, asyncOperation.progress);

        if (asyncOperation.allowSceneActivation == false && asyncOperation.progress >= 0.9f)
        {
            asyncOperation.allowSceneActivation = true;
            //UIManager.Instance.OpenGameUIPanel(EUIPanelName.MainLoding);
        }

        yield return null;
    }

    //UIManager.Instance.gameObject.SetActive(true);
}

//싱글톤 매니저 삭제
public void DestroyManager()
{
    if (destroyQueue != null && destroyQueue.Count > 0)
    {
        int count = destroyQueue.Count;
        for (int i = 0; i < count; i++)
        {
            GameObject go = destroyQueue.Dequeue();
            Destroy(go);
        }
    }
}