라이브 게임 에셋 관리 개선기 - 1.어드레서블 에셋 도입

간식축내는사람·2024년 7월 16일
9
post-thumbnail

인트로


나나이트

게임에는 일반적으로 서비스 앱보다 훨씬 많은 리소스가 사용된다. 이는 게임이 사용자에게 지속해서 재미와 몰입감을 제공하기 위해 풍부한 컨텐츠와 동적인 경험이 필요하기. 만약 우리가 게임을 다운받았는데 게임 캐릭터는 다 똑같이 생긴 졸라맨 뿐이고 공격 커맨드가 하나뿐인 액션게임이었다고 생각해보자. (엥 이거 개꿀잼 게임 슈퍼액션히어로아닌가?) 이렇게 만들어도 게임이 재밌을 수는 있으나, 대부분은 대충 만든 디지털 쓰레기 게임이라고 생각하고 게이머들이 관심을 안 줄 것이다.

게임에는 보통 대규모의 이미지와 3D모델들이 필요하며, 일반적인 서비스 애플리케이션보다 리소스가 집약적으로 사용된다. 현대의 컴퓨터와 모바일 기기가 성능이 개선되었음에도, 여전히 프로그램 최적화에 대한 필요성은 존재한다. 특히 모바일 기기에서는 메모리 관리가 중요한 고려사항으로 남아있다. 리소스를 무분별하게 추가하는 경우 기기의 메모리 한계에 빠르게 도달할 수 있다. 이는 간단한 게임의 개발에서는 큰 문제가 되지 않을 수 있지만, 지속해서 컨텐츠를 업데이트하고 확장해야 하는 게임의 경우 메모리 관리는 필수적인 요소가 된다.

게임이 단순한 실시간 메모리 사용량을 넘어서 장기적으로 업데이트되고, 시즌별 이벤트를 진행하면서, 이전 시즌의 이벤트 에셋들이 항상 필요하지 않은 경우가 많다. 이런 이벤트 에셋들은 다음 시즌에 어떻게 활용될지 불확실하므로 클라이언트 프로젝트에서 완전히 삭제하기는 어려운 상황일 수 있다. 이 경우 이벤트 에셋들을 게임 빌드에서 분리하여 필요할 때만 추가 데이터 다운로드를 통해 플레이어에게 제공하는 방법이 효과적일 수 있다. 이러한 데이터 분리 방식은 단지 이벤트 시에만 유용한 것이 아니라, DLC를 통해 게임 콘텐츠를 추가로 판매할 때에도 활용될 수 있다. 특히 모바일 게임에서는 이러한 분리 기능이 에셋 관리의 핵심적인 요소로 자리 잡을 것이다.

이처럼 게임에는 많은 양의 에셋들이 사용되기 때문에 게임개발을 장기간 안정적으로 진행하기 위해서 프로젝트의 에셋 관리는 필수적이라고 할 수 있다. 이 시리즈에서는 실제 라이브 서비스 중인 게임의 리소스 로드 구조를 개선하고, 게임의 사용성을 높이며 더 많은 리소스를 자유롭게 활용할 수 있도록 하는 다양한 에셋 관리 방법과 실 적용기를 다룰 예정이다. 이번 시리즈는 총 4개의 글로 이루어질 것으로 예상하며, 아래 순서대로 개선 방안을 제시하고 적용해보고자 한다.

1부. 어드레서블 에셋 도입 (현재글)
2부. 메모리 사용 구조 개선 / 리소스 최적화
3부. DLC를 통한 패치 시스템

(이런 내용들이 궁금하다면 미리 팔로우! 눌러주세요~~)

어드레서블 에셋 이란?


어드레서블이 뭔가요?

어드레서블 도입과정을 설명하기 위해서는 유니티의 Resources 폴더와 에셋번들에 대한 이해가 필요한데, 이 글을 읽는 사람이 유니티를 깊게 사용해본 사람이 아니라는 가정하에 간단하게 설명해보고자 한다.

에셋번들

에셋번들은 게임에서 사용하는 에셋들을 묶은 그룹으로써 게임의 최종 빌드파일과 별도로 관리할 수 있는 특징을 가지고 있다. 이처럼 에셋을 묶어서 별도의 파일 개념으로 관리하는 이유는, 프로젝트를 새로 빌드하지 않고도 게임에서 사용하는 에셋의 수정을 편하게 하기 위해서인 경우가 많다.

이렇게 에셋번들을 사용해서 프로젝트를 진행하려면 에셋을 관리해주는 많은 코드가 필요하다. 이 별도의 에셋 파일이 어떻게 묶여서 저장되고, 게임 실행시에는 어디에 있는 에셋을 불러와서 사용할 것이며, 메모리 관리적인 측면에서 게임 시작 시 모든 에셋을 들고 있을 수 없기에 적재적소에 에셋을 로드해야 하는데, 어떤 시점에 메모리에 로드된 에셋을 내리고 새로운 에셋을 로드할지에 대해서 모두 개발자가 코드로 정리를 해줘야 한다. 또한, 아래 설명할 에셋 관리 문제 때문에 에셋을 효율적으로 관리하기 위한 전략을 수립하고, 이에 대한 구현을 진행해줘야 한다.

의존성 관리 문제

칼글이

위 스크린샷처럼 칼을 든 빨간 동글이와 회색이가 각각 별도의 에셋번들로 묶인다고 하자. 각 에셋번들만 로드해도 우리가 빌드했던 오브젝트가 그대로 게임 씬에 생성할 수 있다고 해보자. 이때 에셋번들은 오브젝트를 구성할 서브에셋들을 로드하여 빌드된다. 빨간 동글이로 예시를 들자면, 빨간 동글이 오브젝트에는 (칼, 동글이 리소스)라는 두개의 서브 에셋이 포함된 것이다. 두 오브젝트를 동시에 메모리에 로드했을때 우리의 메모리는 어떻게 될까?

칼글이 메모리

빨간색 배경 -> 빨간 동글이를 포함한 에셋번들이 올라간 메모리
회색 배경 -> 회색이를 포함한 에셋번들이 올라간 메모리

위 이미지에서 보이는 것 처럼, 두 오브젝트가 들고있는 칼이 각각 별도의 에셋으로 구분되어 메모리에 중복되어 올라가게 될 것이다. 이런 식으로 에셋번들이 구성된다면, 에셋과 빌드된 게임을 분리하고자 하는 목적은 달성하겠지만, 메모리를 관리하는 측면에서는 큰 손해를 볼 수 있다. 심지어 이 에셋들을 s3와 같은 외부 서버에 저장한다고 했을 때, 같은 에셋들을 여러번 저장소에 올리고 유저가 다운받아야 하는 상황이 벌어진다. 우리는 지금 지구의 건강을 희생해가며 석탄연료를 통해 전기를 생산해서 쓰고 있는데, 이러한 소중한 전기를 불필요한 중복 메모리에 낭비해서는 안 될 것이다.

에셋 관리 문제

프로젝트가 점점 커지다보면 에셋의 수는 구분하기 힘들 정도로 많아지게 된다. 이때 처음부터 에셋들을 올바른 기준으로 정리해놓지 않았다면 나중에는 에셋이 너무 많아 손을 대기 힘든 수준까지 갈 수 있다. 게임 프로젝트 GUI에서 보이는 에셋들도 관리하기 힘든데, 에셋번들은 어떤 파일에 어떤 에셋들이 포함되어 있는지 한눈에 파악하기가 힘들다.

별도의 자동화 코드를 만들어주지 않으면, 많은 에셋번들을 정해진 기준에 따라 관리해주는 것이 사실상 불가능에 가깝다. 프로젝트 바이너리에 기본적으로 포함되는 에셋들은 프로젝트의 적당한 경로에 폴더를 만들어서 처음 가져올 때 직접 한 번만 옮겨주는 것으로 충분하지만, 에셋번들의 목적은 프로젝트의 바이너리에 포함시키지 않고 별도로 빌드해서 관리하고 사용하는 것이고, 빌드된 번들들이 많아지면 그 복잡도가 늘어날 여지가 크기에, 매번 손으로 관리하는 것은 리스크가 큰 결정이다.

잘못하면 에셋들의 헬파티가 열릴 수 있다!!

unhappy

어드레서블 시스템

어드레서블 시스템은 유니티 게임엔진에서 지원해주는 에셋 관리 시스템으로, 에셋에 특정 어드레스(에셋을 식별하는 키)를 부여하여 에셋을 로드할 수 있는 기능을 지원해주는 시스템이다. "어떠한 이름으로 특정 에셋을 불러온다"라는 기능자체는 매우 간단하지만, 어드레서블 시스템에는 이 외에도 기존 에셋 관리 시스템을 구성할때 가려웠던 부분들을 긁어주는 기능들을 포함하고 있다.

  • 내부적으로는 여전히 에셋번들 단위로 그룹핑하여 사용
  • '어드레서블 그룹'단위로 에셋번들을 관리, 그룹단위로 세부 설정 가능
  • 에셋 관리 룰을 통한 전체 검사(에셋 의존성 관리도 가능)
  • 원하면 에셋들을 앱 빌드 자체에 포함 가능

에셋 그룹 관리 지원

위에서 에셋번들을 사용하면 에셋을 그룹별로 깔끔하게 정리하기 힘들다고 했었다. 어드레서블을 이용하면 유니티 에디터 자체에서 'Addressables Groups' 윈도우를 통해 에셋들을 원하는 그룹으로 묶어서 쉽게 관리할 수가 있게 된다. 그리고 이렇게 관리된 그룹별로 각기 다른 에셋번들을 관리하기 위한 세부 설정을 할 수가 있게 된다. 아래 스크린샷이 그 예시이다.

좌측의 윈도우는 현재 프로젝트에서 사용하고 있는 어드레서블 그룹의 목록이고, 우측의 'Inspector'윈도우는 현재 선택된 에셋 그룹에 대한 세부 설정을 나타내고 있는 윈도우이다.

설정중 가장 이해하기 쉬운 것은 빨간 동그라미로 표시한 'Build & Load Paths'이며 이 에셋 번들을 빌드후 에셋번들을 저장할 위치와 이 에셋을 로드할 위치를 설정해주는 부분이다. 현재 'Local'로 설정된 있는 옵션을 'Remote'로 바꾸면 원하는 웹 저장소에 에셋을 저장하기 위한 설정을 할 수도 있다. 이를 통해 특정 에셋그룹은 로컬에 빌드하고, 특정 그룹은 원격 저장소에 빌드하는 등의 셋팅도 가능하다.

현재 나인크로니클에서 사용되고 있는 어드레서블 그룹 목록이다. ActorPrefab, CharacterSpine, LocalAssets이라는 3개의 그룹으로 나뉘어져 있다.

  • ActorPrefab: 현재 게임에 사용될 몬스터 오브젝트들을 관리하고 있는 그룹. 이 오브젝트들은 1개의 오브젝트가 1개의 에셋번들로 빌드되어 관리될 것이며, s3 클라우드 저장소에 에셋을 저장해 두었다가 클라이언트 빌드 없이 패치해서 사용할 수 있도록 구성을 할 것이기 때문에 Remote 저장소를 저장경로로 셋팅할 것 이다.
  • CharacterSpine: 게임 내에 사용될 캐릭터의 장비 파츠들을 관리하는 그룹. ActorPrefab 그룹과 동일한 셋팅.
  • LocalAssets: 빌드에 포함되어 프로젝트 내부에서 사용할 에셋들을 관리하는 그룹. 모든 에셋이 1개의 에셋번들로 묶여 거의 상시 메모리에 올라가 있는 에셋번들로 생각하고 있으며, 이처럼 프로젝트 내에서 상시 사용할 에셋들을 한개의 그룹으로 묶어서 어드레서블 그룹으로 관리한 이유는 위에서 이야기한 '에셋의 의존성을 관리'하여 중복 리소스 로드를 막기 위함이다.

의존성 문제 해결

어드레서블은 패키지에서 제공해주는 툴로 동일한 에셋이 여러 에셋번들에 묶여있으면, 중복으로 로드될 수 있는 리소스를 별도의 번들로 묶어 이 리소스를 포함해야 하는 에셋들에게 자신의 번들을 참조하게 하는 식으로 중복 리소스 로드 문제를 방지할 수 있다. 하지만 이와 같은 과정을 어드레서블에서 자동으로 진행해주는건 아니고, 별도의 툴을 통해 개발자가 쉽게 수정할 수 있도록 도와주는 방식이다. 이 툴을 통해 에셋의 의존성을 파악하고 중복 리소스가 로드되는 문제를 아래 프로젝트 소개 이후 직접 진행해볼 예정이다.

공통된 인터페이스

어드레서블은 에셋을 관리해주는 '공통된 인터페이스'를 제공해준다는 측면에서도 이득이 있다. 만약 팀에서 자체개발한 에셋 관리 툴을 사용하다보면, 다른 팀이나 회사의 에셋 관리 툴은 다른 구조, 다른 용어를 사용하여 구현될 가능성이 높은데, 이러면 새로운 에셋 관리 구조를 파악하는데 시간이 많이 들 수 있다. (그리고 이러한 툴들은 엄청난 하드코딩으로 구현되어 있을 가능성이 높다)

이러한 이유로 기존 에셋번들을 관리하기 위한 복잡도를 어드레서블을 통해 크게 낮출 수 있다. 러닝커브가 있음에도 많은 팀이 어드레서블을 도입하려는 이유가 이런 것이며, 한번만 배워놓으면은 동일한 방식으로 여러 프로젝트의 에셋을 관리하기도 쉬울 것이다.

(혹시 안 즐거우시다면 죄송합니다)

패치 시스템

위에서 말했듯이 DLC를 통한 패치 시스템이 모바일 프로젝트의 에셋 관리에서 가장 핵심적인 부분이 될 가능성이 높다. 이미 패치 시스템이 도입된 다른 게임들을 예시로 그 이유를 알아보자.

프커업뎃

"게임 프린세스 커넥트! Re:Dive 추가 다운로드 팝업"

위 게임은 내가 몰래 숨어서 플레이하고 있는 미소녀 수집형 게임인 '프리코네'의 업데이트 팝업이다. 2024년 4월 18일 기준, iOS 앱스토어에 업로드 된 이 게임의 기본 크기는 290.1MB 이정도는 '일반적인 서비스 어플'들보다 약간 큰 정도이다. 하지만 게임을 실제로 플레이하기 위해서는 추가적으로 10.73GB의 데이터를 다운로드 받아야 한다.

이처럼 대부분의 모바일 게임들은 실제 앱스토어에는 앱을 구동시키기 위한 코드와 최소한의 에셋들만 포함되어있는 빌드파일을 올려놓고, 대부분의 핵심 에셋들은 별도로 다운받는 구조로 제작되어 있다. 위에 예시로 든 프리코네의 경우, 신규 캐릭터 획득시마다 그 캐릭터와 관련된 리소스나 게임 스토리를 보기 위한 리소스도 필요시마다 추가로 다운로드 하고 있어 실제로 게임 플레이를 하면서 다운받는 리소스의 양은 훨씬 많아질 수 있다.

이렇게 외부에서 리소스를 다운 받는 이유는 다음과 같다.

  1. 앱 스토어에서 보이는 앱 용량이 진입장벽으로 느껴질 수 있다.
  2. 안드로이드는 apk로 앱을 업로드 시 150MB 이하로 유지해야 하는 전통이 있었다.
  3. 그리고 패치 시스템을 이용하면 번거로운 앱 심사 과정을 회피하고 업데이트를 할 수 있다

사실 개인적인 입장에서는 3번이 핵심이라고 생각한다. 게임개발을 하다 보면 추가로 빌드 과정을 진행하는 것도 엄청난 일이며 이를 앱스토어에 심사받고 정해진 기간까지 심사가 통과할지 기도하는 일도 회사 팀원들의 마음을 힘들게 한다. 이 과정은 많은 시간과 정성이 필요하며, 때로는 예상치 못한 심사 지연이나 거절 사유를 해결하기 위해 추가적인 노력(과 야근)이 필요할 수 있다.

앱 패치 심사

애플의 앱스토어 심사는 구글 마켓에 비해
까다롭고 오래 걸리는 것으로 유명합니다.
몇 시간 정도 걸리는 안드로이드 심사와 달리

애플 앱 심사는 약 일주일 정도 걸리는데요.
일주일을 기다린 앱 심사 결과가
거부(reject)면 재심사까지 더해 약 2주일,
한번 더 reject되면 3주까지 늘어나기도 하죠.

그렇기에 앱스토어 심사를 가장 빨리 통과하는
방법은 거부(reject)당하지 않는 거죠.
- 디스이즈게임, [카드뉴스] 거부를 거부한다! 앱스토어 심사에서 리젝당하지 않는 법

https://www.thisisgame.com/webzine/nboard/257/?n=61254?n

모바일게임을 만들고 앱 마켓에 등록하기 위해서는 필수적으로 심사 과정을 거쳐야 한다. 앱 심사는 대부분 첫 심사가 매우 까다롭고 이후 심사는 상대적으로 너그럽게 진행되는 편인 것 같다. 하지만 첫 심사가 아니라고 방심하면은 안 된다. 앱 심사 거절은 언제 어디서 일어날지 모른다.

만약 코드 수정이 아닌 단순 리소스 수정이라면, 위와 같은 패치 시스템을 이용해서 앱 심사를 우회하고 바로 유저들에게 콘텐츠 업데이트를 할 수 있다. 한번 DLC 업데이트에 익숙해지고 나면, 단순 리소스 수정 같은 일로 하루 이상의 딜레이를 가질 수 있는 앱 심사 과정을 거쳐야 하는 것이 매우 번거로운 일로 느껴질 수 있을 것이다.

나인크로니클에도 이러한 패치 시스템이 도입될 것으로 예상하며, 이러한 작업은 후에 진행한 후 따로 글을 작성해 볼 예정이다. 이에 대해서 당장 시도해본 내용은 아래 프로젝트 소개 후 녹화해둔 영상과 함께 이야기해보도록 하겠다.

iOS 리소스 다운로드

iOS의 경우 이러한 리소스 다운로드 정책에 대해 민감하게 심사를 진행하는 편이었다. 나는 과거 iOS에서 어플을 개발하여 업로드하고, 첫 심사를 통과하고 앱 관리를 해오다가 추후 외부 게임 리소스들을 어셋번들로 분리하고 다운로드해서 사용하는 DLC 구조를 구현한 적이 있었다. 그 이후 테스트를 마치고 앱 업데이트 요청을 진행했었는데, 이때 약 일주일 동안 고통스러운 리젝대응기간을 보냈던 기억이 있다. iOS 심사 지침을 확인해보자.


4.2 최소 기능

  • 4.2.3
    • (i) 앱은 다른 앱을 설치할 필요 없이 단독으로 작동할 수 있어야 합니다.
    • (ii) 초기 실행 시 작동하기 위해 추가 리소스를 다운로드해야 하는 앱은 다운로드하기 전에 리소스 크기를 공개하고 사용자에게 승인을 요청해야 합니다.

변경된 심사 지침(22.06.06 문서)

  • 4.2.3: 출시 초기에도 앱이 충분히 기능할 수 있도록 바이너리에 풍부한 콘텐츠가 있어야 한다는 요구 사항을 삭제했습니다.

iOS에서 외부 리소스 다운로드와 관련된 조항은 바로 '4.2 최소기능' 파트일 것이다. 먼저 맨 처음에는 사용자가 다운받는 리소스 크기를 명시하지 않고 리소스를 다운로드시켜서 리젝을 받았었다. 이에 대한 부분은 콘텐츠 다운로드를 위한 ok/cancel 팝업을 추가하고, 다운받을 리소스 용량을 표기하였다. 해결했다고 생각하고 다시 앱 심사를 진행했었다. 이후 저 삭제된 '4.2.3' 조항 때문에 엄청난 삽질을 거듭하다 통과했지만, 여기서 라떼이야기는 생략하도록 하겠다.

나인크로니클(9C)

9c

요번 글에서 에셋 로드 구조를 개선할 게임은 오픈소스로 개발하고 서비스되고있는 풀 블록체인 기반의 2D RPG 게임인 나인크로니클이다. 풀 블록체인 기반 게임이라는 것이 핵심적이고 재미있는 특징이지만, 이번 시리즈에서는 에셋 로드와 관련된 클라이언트 영역에 대해서만 다룰 예정이다. 이 프로젝트는 오픈소스로써 실제로 작업한 내용들을 직접 확인해볼 수 있다. 이번 시리즈에서도 작업 PR의 링크를 달거나 코드 일부분을 글에 첨부해볼 예정이다.

전투

나인크로니클은 기본적으로 스텟을 기반으로 한 전투 시뮬레이션 게임이다. 체인 위에 기록된 나의 캐릭터의 스텟과 스킬을 기반으로, 테이블 데이터에 기록된 스테이지의 몬스터와 랜덤을 기반으로 한 전투가 시뮬레이션 되며, 그 결과가 클라이언트에게 전송되어 전투 과정을 렌더링하여 위 스크린샷처럼 전투를 진행하게 된다.

어드레서블 패키지 적용

먼저 사용되는 리소스 중 다른 기능들과 의존성이 적은 부분을 찾아서 먼저 적용해보고자 했다. 그중 많은 리소스를 사용하면서도 가장 독립적인 부분이 바로 '스테이지의 몬스터 리소스'라고 판단되어 해당 부분을 우선하여 어드레서블을 이용하여 로드하도록 분리하고, 리모트 다운로드 테스트까지 진행할 계획을 세웠다. 먼저 어드레서블 리소스를 관리하기 위한 리소스 매니저를 프로젝트에 추가하였다.

리소스 매니저

개인적인 경험상 에셋을 여러 스크립트에서 로드하고 릴리즈하는 과정을 진행하다 보면, 꼭 리소스가 제대로 관리되지 못하고 릴리즈되지 못하는 에셋들이 생기게 되는 것 같았다. 어드레서블에서 에셋은 레퍼런스 카운트를 기반으로 관리되며(c++의 스마트포인터를 생각하면 쉬울 것), 이 에셋의 핸들을 관리해주는게 가장 기본적인 요소 중 하나이다.
(레퍼런스카운트 관리 및 어드레서블의 자세한 내용은 나중에 별도의 글로 다뤄볼까 한다)

나는 이러한 에셋 핸들과 레퍼런스 카운트 관리를 위하여 ResourceManager를 구현해서 사용하는 것을 선호한다. 프로젝트마다 디테일한 에셋 로드/언로드 타이밍은 다르겠지만, 프로젝트 전역적으로 사용되는 소수의 에셋을 제외하고는 대부분의 에셋은 씬 전환 시 날려버려도 상관 없다고 생각하기에 씬이 바뀔 때마다 ResourceManager에 캐시된 모든 에셋들을 날려주는 식으로 구현했다.

https://github.com/planetarium/NineChronicles/pull/4725

Battle Renderer

구현된 리소스 매니저를 기반으로 에셋을 로드하는 시스템을 적용하였다. 자세한 작업 내용은 아래 PR의 Desription에 적어두었다. 나인크로니클의 전투가 턴 기반이라는 것을 생각해보면, 처음 전투를 시작할 때 현재 플레이어의 스텟과 도전하고자 하는 스테이지의 데이터를 기반으로 미리 전투를 시뮬레이션할 수 있으며, 이때 시뮬레이션 된 배틀을 렌더링할 때 필요한 리소스들을 긁어와 미리 로드해둘 수 있다.

배틀로그

위는 배틀 시작 시 클라이언트에서 받는 시뮬레이션 결과이며, SpawnWave이벤트 안에는 어떠한 몬스터가 스폰되는지에 대한 정보가 모두 담겨있다. 이를 통해 로딩 타이밍에 스폰될 몬스터 리소스들을 미리 로드하였다가 필요한 타이밍에 스폰하면, 클라이언트는 필요한 메모리만 올려서 사용하며 몬스터 생성 시 리소스를 메모리에 올리기 위한 부하(프레임드랍) 없이 게임을 진행할 수 있게 된다. 이러한 작업의 핵심 로직은 아래 스크립트에서 확인할 수 있다.

// in BattleRenderer.cs

public IEnumerator LoadStageResources(BattleLog battleLog)
{
    ReleaseMonsterResources();
    yield return LoadMonsterResources(battleLog.GetMonsterIds());
    _onStageStart?.Invoke(battleLog);
}

// TODO: 필요한 것만 로드
private IEnumerator LoadMonsterResources(HashSet<int> monsterIds)
{
    var resourceManager = ResourceManager.Instance;
    foreach (var monsterId in monsterIds)
    {
        yield return resourceManager.LoadAsync<GameObject>(monsterId.ToString()).ToCoroutine();
        loadedMonsterIds.Add(monsterId);
    }
}

public void ReleaseMonsterResources()
{
    var resourceManager = ResourceManager.Instance;
    foreach (var loadedMonsterId in loadedMonsterIds)
    {
        resourceManager.Release(loadedMonsterId.ToString());
    }
    loadedMonsterIds.Clear();
}
  1. 먼저 스테이지 시작 시 전투 시뮬레이션 결과를 불러와 렌더링에 필요한 모든 에셋목록을 긁어온다
  2. 로드할 에셋들을 차례대로 비동기로 로드하며, 로딩이 끝날 때까지 로딩 UI를 출력한다
  3. 모든 리소스 로드가 끝나면 OnStageStart 이벤트를 실행해 구독한 객체들에 메시지를 전송한다.
  4. 스테이지가 끝나면 ReleaseMonsterResources 메서드로 모든 리소스를 해제해준다.

https://github.com/planetarium/NineChronicles/pull/4736

어드레서블 패키지를 적용하면 모든게 다 해결될까?

그건 아니다. 패키지를 적용하더라도 에셋을 직접적으로 관리해주는 코드가 필요하다. 아까 에셋번들을 이야기하면서 결국 어떤 에셋들을 묶어서 사용할 것인지, 에셋을 언제 메모리에 올릴지 등에 대해 많은 관리 코드가 필요하다고 했는데 이건 어드레서블을 사용해도 근본적으로 해결되지 않는 영역이다. 어드레서블을 사용해도 결국 이러한 관리 코드가 필요하기 때문에, 굳이 러닝 커브가 있는 어드레서블을 사용하지 않고 순수 에셋번들만을 이용해서 프로젝트를 진행하는 때도 있다.

하지만 앞으로 오랜 시간 게임개발자로 밥 벌어먹고 살고자 한다면 에셋 관리 시스템은 어드레서블로 해보는 것을 추천한다. 실제로 많은 팀에서 사용하고 있고, 사용하고 있지 않다면 도입을 고려하고 있는 팀들이 많아서 면접에서 단골로 물어보는 주제이기도 하다. 소형팀이든, 대형팀이든 이런 식으로 에셋 관리하는 구조를 구축해볼 경험이 있다면 좋은 가산점이 될 것이다.(그리고 이러한 주제가 일반적인 게임개발 취준생들이 쉽게 고려해보지 못하는 주제이기도 해서 좋은 점수를 받기 좋은 포인트라 생각한다.)

중복 사용 에셋 감지

이는 어드레서블의 Analyze Rules중 "Check Resources to Addressable Duplicate Dependencies" 기능을 통해 확인할 수 있다. 이 기능은 Unfixable Rules 중 하나로, 이는 어드레서블에서 툴을 통한 검사는 해주겠지만, 자동으로 고칠 수는 없어서 개선을 원한다면 개발자가 수동으로 작업해야 하는 분석 규칙임을 뜻한다. 현재 작업 중인 프로젝트에 이 규칙을 검사해보면..

짜잔.. 위에서 말한 어드레서블에서 중복으로 로드될 수 있는 에셋들을 감지하는 툴을 통해 에셋을 검사해 본 결과이다. 현재 905개의 에셋에서 중복으로 로드될 수 있는 서브에셋들의 목록들이 나왔고, 우측에 표시된 숫자들은 이 에셋에서 중복 사용 중인 서브 에셋들의 개수를 나타낸다. 현재 보이는 스크린샷에서 한 에셋당 4개 정도의 서브에셋들이 중복으로 사용되고 있으니 대략 계산해서 3,600개의 서브 에셋들이 중복으로 로드될 수 있는 상황이라는 것이다.

망곰울먹

이는 안타깝지만, 처음부터 어드레서블 에셋을 도입하여 사용하지 않아 생긴 부작용이라고도 볼 수 있다. 기존 레거시한 방식으로 다량의 에셋들이 관리되고 있었기에, 어드레서블로 관리되는 에셋이 일부분 생김에 따라 기존 어드레서블에서 관리되던 에셋과 연관성이 있던 서브 에셋들이 이처럼 중복 로드될 가능성이 있는 에셋으로 감지되는 것이다. 실제로 현재 감지된 duplicate dependencies의 파일 목록을 자세히 보면 경로가 'Assets/Resources..'로 시작하는 것을 볼 수 있다. 이 경로에 있는 에셋들은 모두 레거시한 방법으로 빌드시 무조건 포함되는 에셋들에 해당한다.

실제 라이브 서비스 중인 프로젝트이기도 하고, 관련해서 피쳐 작업들이 계속 진행되고 있기에 이러한 에셋들을 한번에 어드레서블로 관리되도록 수정하는 것은 거의 불가능에 가깝다고 생각한다. 따라서 단기적으로는 메모리에 중복으로 로드되는 에셋들이 많더라도 감수하고 개선 작업을 천천히 진행해야 할 것이다. 이것이 가능하면 어드레서블 에셋을 초기부터 도입해야 하는 이유 중 하나라고 볼 수 있다. 유니티 메뉴얼에도 아래와 같은 내용이 적혀있다.

유니티 메뉴얼

위 메뉴얼 링크: https://docs.unity3d.com/kr/Packages/com.unity.addressables@1.21/manual/AddressableAssetsMigrationGuide.html

에셋 패치를 하려면?

에셋을 패치 하려면 유저들이 앱 실행 중 항상 접근할 수 있는 웹 서버에 사용할 에셋들을 업로드해야 한다. 나는 회사에서 적극 사용 중인 AWS에서 제공해주는 S3 버킷에 에셋을 업로드하고 게임에서 이를 로드해서 사용해보는 테스트를 하였다.

(위 영상에서 '바로 로드 안됨'이라고 나오는 부분의 문제는 해결했는데, 어이없게도 에셋 로드를 하고 로드가 완료되는 걸 기다리지 않고 오브젝트를 생성하고 있었다.)

위 어드레서블의 장점에서 'Remote'로 에셋을 저장해주는 세팅을 하면 에셋이 원격 저장소에 저장된다고 말했다. 위 영상이 바로 ActorPrefab 그룹을 Remote에 저장되도록 세팅하고 그 에셋들을 s 3원격 저장소에 올려서 사용하는 영상이다. 영상을 보면 알 수 있듯이, 어드레서블에서 제공해주는 기능만을 이용하면 프로젝트의 외딴곳에 에셋이 빌드되고 이를 수동으로 손으로 옮겨서 S3 버킷에 업로드해주는걸 볼 수 있을 것이다.

또한 현재 클라이언트에 있는 에셋이 최신 에셋인지 파악하기 위해 해시 값들을 관리해야 하고, 이러한 해시 값을 관리해주는 카탈로그 개념에 대해 인지하고 있어야 한다. 이에 대한 경험과 자세한 설명들은 추후 DLC를 통한 패치 시스템에 대한 글을 쓸 때 다뤄보도록 하겠다.

에셋 로드 시스템을 바꾸는 겸 시스템 개편

또한, 에셋 로드 시스템을 어드레서블 패키지로 바꾸는 겸 기존의 에셋 로드 시스템들을 개선하고 있다. 이는 주로 메모리 관리와 관련된 부분으로, 이어서 바로 작성할 2번째 글인 '메모리 사용 구조 개편' 에서 자세히 다뤄보도록 하겠다. 해당 글에서는 mac의 Xcode를 통해 앱을 프로파일링 하며 실제 메모리 사용량과 앱 용량까지 절감시키고 있는 이야기를 살펴볼 수 있을 것이다.

끝

profile
간식주세요

1개의 댓글

comment-user-thumbnail
2024년 7월 16일

짜장면한테 이글을 보여줬더니 짜장면이 비벼지며 에셋번들에 패킹되었습니다.

답글 달기