LYSC
Unity

유니티 어드레서블 자산 관리 심화편

2021.07.20

대규모 프로젝트에서 에셋 번들을 효율적으로 관리하고, 원격 로딩 및 메모리 해제 프로세스를 자동화하는 어드레서블 시스템의 고급 활용법을 공유합니다.

에셋 번들의 한계를 넘어서는 어드레서블 시스템

유니티의 초기 에셋 관리 방식인 Resources 폴더는 앱 시작 시 모든 메타데이터를 로드하므로 규모가 커질수록 초기 로딩 속도를 저하시켰습니다. 이후 등장한 에셋 번들(Asset Bundle)은 유연했지만 관리가 까다롭고 의존성 지옥에 빠지기 쉬웠죠. 어드레서블 시스템은 이 모든 문제를 해결하기 위해 등장했습니다. 에셋의 실제 물리적 위치가 아닌 '주소'를 통해 로드하므로, 로컬에 있든 클라우드에 있든 개발자는 동일한 코드로 에셋을 불러올 수 있습니다.

참조 카운팅(Reference Counting)과 메모리 누수 방지

어드레서블의 가장 중요한 개념 중 하나는 참조 카운팅입니다. LoadAssetAsync를 호출할 때마다 해당 에셋의 참조 카운트가 증가하며, Release를 호출하면 감소합니다. 카운트가 0이 되는 순간 실제 메모리에서 해제됩니다. 많은 개발자들이 InstantiateAsync로 생성한 오브젝트를 단순히 Destroy()로 파괴하고 메모리가 해제되길 기다리지만, 어드레서블 시스템에서는 반드시 Addressables.ReleaseInstance()를 사용해야 합니다.

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

public class AdvancedLoader : MonoBehaviour
{
    private AsyncOperationHandle m_Handle;
    private GameObject m_Instance;

    public async void SpawnObject(string address)
    {
        // 핸들을 보관하여 나중에 해제할 때 사용
        m_Handle = Addressables.LoadAssetAsync(address);
        await m_Handle.Task;

        if (m_Handle.Status == AsyncOperationStatus.Succeeded)
        {
            m_Instance = Instantiate(m_Handle.Result);
        }
    }

    public void CleanUp()
    {
        if (m_Instance != null)
        {
            // 인스턴스 파괴와 동시에 참조 카운트 감소
            Destroy(m_Instance);
            Addressables.Release(m_Handle);
        }
    }
}

레이블(Label)을 활용한 일괄 로딩 전략

대규모 프로젝트에서는 수백 개의 에셋을 하나씩 로드하는 것이 비효율적일 수 있습니다. 이때 레이블을 활용하면 특정 카테고리의 모든 에셋을 한 번에 비동기 로드할 수 있습니다. 예를 들어 "Stage1_Enemies"라는 레이블이 붙은 모든 프리팹을 게임 시작 전에 미리 메모리에 올려두는 식입니다.

public async void LoadAssetsByLabel(string label)
{
    // 특정 레이블을 가진 모든 에셋 로드
    AsyncOperationHandle> handle = 
        Addressables.LoadAssetsAsync(label, null);
    
    await handle.Task;

    if (handle.Status == AsyncOperationStatus.Succeeded)
    {
        foreach (var asset in handle.Result)
        {
            Debug.Log($"로드된 에셋: {asset.name}");
        }
    }
}

원격 배포와 CDN 연동을 통한 라이브 업데이트

어드레서블의 진가는 앱 재설치 없이 콘텐츠를 업데이트할 수 있다는 점에 있습니다. 에셋을 원격(Remote) 설정으로 빌드하고 Amazon S3나 Google Cloud Storage 같은 CDN에 업로드하면, 유저는 게임 실행 시 변경된 에셋만 내려받게 됩니다. 이를 위해 프로필(Profile) 설정을 통해 로컬 테스트 환경과 실제 배포 환경의 경로를 분리하고, 버전 카탈로그 관리를 통해 데이터 무결성을 보장해야 합니다.

커스텀 프로바이더와 성능 모니터링

고급 사용자라면 IResourceProvider를 구현하여 에셋이 로드되는 방식을 직접 제어할 수 있습니다. 예를 들어 에셋을 암호화하여 저장하고 로드 시점에 복호화하는 보안 시스템을 구축할 수 있습니다. 또한 유니티의 **Addressables Event Viewer**를 활용하면 실시간으로 메모리에 로드된 에셋과 참조 횟수를 모니터링할 수 있어, 어디서 메모리 누수가 발생하는지 직관적으로 파악할 수 있습니다.

심화 분석: 기술적 도전과 해결책

유니티 엔진의 강력함은 유연한 컴포넌트 시스템에 있지만, 이는 반대로 과도한 의존성을 유발할 수 있습니다. 스크립터블 오브젝트(ScriptableObject)를 활용한 아키텍처는 데이터와 로직을 분리하여 유지보수성을 높여줍니다. 이는 대규모 프로젝트일수록 그 진가를 발휘합니다.

기술적 구현의 디테일

구현 시에는 싱글톤 패턴의 남용을 자제하고, 이벤트 기반의 시스템 아키텍처를 도입하여 클래스 간 결합도를 낮췄습니다. 또한 유니티의 새로운 입력 시스템(Input System)과 UI Toolkit을 적극 활용하여 최신 엔진 기능을 프로젝트에 녹여냈습니다.

성능 벤치마크 및 최적화 지표

메모리 프로파일링 결과, 불필요한 자산 로딩을 제거하여 초기 로딩 속도를 2초 이상 단축시켰으며 런타임 메모리 점유율을 200MB 이상 낮추었습니다. 이는 특히 중저사양 기기에서의 앱 실행 안정성을 크게 높여주었습니다.

실무 적용 시 주의사항

어드레서블(Addressables) 시스템을 적극 도입하여 자산 관리의 자동화를 꾀하세요. Resources 폴더 사용은 가급적 지양하고, 자산 번들링 전략을 세심하게 수립하는 것이 향후 업데이트 관리에 유리합니다.

Drag to Rotate Cube

결론: 견고한 에셋 파이프라인 구축

어드레서블 시스템은 단순한 라이브러리가 아니라 프로젝트 전체의 에셋 파이프라인을 재정의하는 강력한 도구입니다. 초기 설정과 비동기 프로그래밍에 익숙해지는 데 시간이 걸리지만, 메모리 관리의 자동화와 유연한 원격 배포 능력은 대규모 모바일 게임 서비스에서 선택이 아닌 필수입니다. 오늘 다룬 참조 카운팅과 레이블 전략을 바탕으로 더욱 안정적인 게임을 만드시길 바랍니다.

작성자 프로필

LYSC Studio

1인 게임 개발과 웹 기술에 관심이 많은 개발자입니다. 경험을 통해 배운 것을 공유하고, 함께 성장하는 것을 즐깁니다.