멈추지 않고 끈질기게
[Unity] Addressable 기능 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 Unity 2021.3.15f1 버전을 기준으로 작성되었습니다.
이번 포스팅에서는 유니티의 새로운 리소스 로드 기능 Addressable에 대해 알아보겠습니다.
1. Addressable 등록
우선 Addressable 기능은 Package Manager에서 'Addressables'로 검색하여 추가할 수 있습니다.
또한 프로젝트에서 Addressable 기능을 처음 사용한다면 그룹을 추가해주어야 합니다. Asset Management -> Addressables -> Groups로 들어간 뒤 'Create Addressable Settings' 버튼을 누르면 디폴트 그룹이 생성됩니다.
해당 기능을 추가하고 나면 Assets 폴더 내의 스크립트를 제외한 객체(파일) 클릭 시 인스펙터 창 상단에 Addressable 체크박스가 노출되는 것을 확인할 수 있습니다. 체크 시 자동으로 Addressable 그룹에 등록되며 이는 Addressables Group 창에서 확인할 수 있습니다. Addressables Group 창에서는 해당 파일의 이름, Asset Type, Path 등이 나타나며, 이름의 경우 직접 수정할 수 있습니다. 그룹을 추가로 생성하여 리소스 타입에 따라 분류할 수 있으며, Addressable 등록 시 디폴트로 들어갈 그룹도 변경할 수 있습니다.
여기에 등록한 이름이 후술할 Addressables.LoadAssetAsync<T>()의 매개변수(key)가 됩니다. 주목할 점은 등록한 객체를 다른 폴더로 이동시킬 경우 Path가 자동으로 갱신된다는 점입니다. 이 덕분에 기존의 Resources.Load()와 다른 강점을 가지며, 이는 밑에서 자세하게 설명하겠습니다.
또한 Addressable 기능으로 로드한 리소스를 체크할 수 있는 프로파일러가 따로 존재합니다. 사용하려면 우선 프로젝트 창에서 Assets -> AddressableAssetsData -> AddressableAssetSettings 에서 Send Profile Events 옵션을 체크해주어야 합니다. 이벤트 뷰어는 Asset Management -> Addressables -> Event Viewer 를 통해 열 수 있으며, 실행 시 현재 로드된 리소스들의 목록 및 레퍼런스 카운트를 확인할 수 있습니다.
2. 리소스 로드 / 해제
Addressable 그룹에 등록한 객체는 Addressables.LoadAssetAsync<T>() 함수로 로드할 수 있으며, 이름에서 알 수 있듯이 비동기 함수입니다. UnityEngine.AddressableAssets 네임스페이스에 포함되어 있으며, 반환형인 AsyncOperationHandle 을 이용하려면 UnityEngine.ResourceManagement.AsyncOperations까지 함께 사용해주는 것이 좋습니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class AddressableTest : MonoBehaviour
{
void Start()
{
// 마우스 커서 텍스처 바꾸기
AsyncOperationHandle handle = Addressables.LoadAssetAsync<Texture2D>("Cursor_Mining");
// 완료 시점에 실행할 내용 callback으로 등록
handle.Completed += (op) =>
{
Cursor.SetCursor(handle.Result as Texture2D, Vector2.zero, CursorMode.Auto);
Debug.Log("Complete");
};
// 해제
Addressables.Release(handle);
}
}
위 코드는 Addressable로 등록한 텍스처를 로드하여 마우스 커서의 텍스처를 변경하는 예시입니다. AsyncOperationHandle은 로드 완료 시 실행될 내용을 포함하는 대리자 Completed를 포함하고 있으며, 여기에 완료 시점에 실행하고자 하는 내용을 등록할 수 있습니다. 단, Completed 대리자가 Action<AsyncOperationHandle> 타입이므로 매개변수를 하나 사용하는 함수여야 합니다. 위 예시에서는 Cursor_Mining으로 등록된 텍스처를 로드하고, Completed에 람다 식을 등록하여 로딩 완료 시 해당 텍스처로 커서 이미지를 변경하고 로그를 출력하도록 했습니다.
사용이 완료된 리소스라면 Addressables.Release()로 더이상 참조하지 않음을 전달해주어야 합니다. Addressable을 통해 로드된 리소스는 메모리에 올라간 뒤 Reference Count가 0이 되는 순간 해제되며, Release() 함수가 Reference Count를 낮추는 역할을 합니다. 나중에 필요할 때 해제하려면 반환값을 AsyncOperationHandle 타입 변수에 저장해두는 것이 좋겠습니다.
또한 같은 키로 등록한 리소스를 LoadAssetsAsync<IList<T>>()를 통해 한꺼번에 로드할 수도 있습니다. 해당 함수의 경우 매개변수로 키와 함께 callback 함수를 전달해야 하며, 해당 콜백 함수는 각 리소스가 로드될 때마다 호출됩니다. LoadAssetAsync와 마찬가지로 AsyncOperationHandle 타입을 반환하며, 해당 핸들러의 Completed 대리자에 모든 리소스의 로드가 완료되었을 때의 동작을 등록해둘 수 있습니다.
(현재 LoadAssetAsync()로는 정상 로드되는 키를 LoadAssetsAsync()에 사용했을 때는 InvalidKeyException이 발생하는 이슈가 있어 확인중입니다.)
단, 중복된 키를 LoadAssetAsync<T>()를 통해 단일 로드할 경우 문제가 생길 수 있습니다. 아예 존재하지 않는 키를 호출하는 경우에는 AddressableAssets.InvalidKeyException이 발생하여 콘솔에서 금방 확인할 수 있지만(사진 5 참조), 타입이 다른 리소스를 같은 키로 등록한 경우 별도의 예외는 발생하지 않으면서 의도와 다르게 동작하여 원인 파악이 어렵습니다.
기존의 2D 프로젝트에 Addressable 기능을 적용하는 과정에서 이미지와 오디오 리소스를 등록했는데, 이 때 일부 무기의 이미지와 오디오를 같은 이름으로 등록하는 바람에 해당 무기들의 sfx가 로드되지 않는 이슈가 발생했습니다. 별도의 예외가 발생하지도 않고, 확인해보니 sfx를 저장하는 배열의 일부 원소들이 로드가 끝났음에도 null 상태여서 발생하는 이슈였는데 원인을 찾느라 한참 헤맸습니다. LoadAssetsAsync()를 통해 한꺼번에 로드할 계획이 없다면, 단일 로드할 리소스의 키는 중복되지 않게 등록하는 것이 좋겠습니다.
3. Addressable의 장단점
Addressable의 장점에 대해 얘기하자면 우선 기존의 Resources 클래스를 이용한 로드 방식의 단점에 대해 알아보아야 합니다.
Resources.Load(), Resources.LoadAsync()는 Assets 폴더 내에 존재하는 Resources 폴더의 리소스들을 로드하는데 사용할 수 있으며, 매개변수로 경로를 정확히 입력해주어야 합니다. 폴더명(Resources) 및 경로에 오탈자가 있다면 제대로 동작하지 않을 뿐더러, Resources 폴더 내에서 객체의 위치를 변경할 경우 이를 로드하는 부분에서도 경로를 수정해주어야 하는 불편함이 있습니다. 하지만 Addressable로 등록한 객체의 경우, 위치 변경 시 Path가 자동으로 갱신되며 해당 객체의 Key를 매개변수로 로드하기 때문에 객체의 위치를 바꾸어도 로드하는 부분을 수정할 필요가 없습니다.
또한 Resources 폴더에 포함된 리소스들은 실제 사용 여부에 상관없이 전부 빌드에 포함됩니다. 만약에 Resources 폴더에 10GB 분량의 리소스를 넣어두고 실제로 사용하는 리소스는 그 중 1GB밖에 안되더라도, 빌드 시 10GB 분량의 리소스가 전부 포함됩니다. 이런 단점을 피하려면 Resources 폴더에는 실제로 사용하는 리소스만 넣어두고, 나머지 리소스들은 별도의 폴더에 저장해두는 불편한 과정이 필요합니다. 반면에 Addressable을 이용하면 폴더에 상관없이 등록한 객체만 빌드에 포함되므로 이러한 단점을 해결할 수 있습니다.
다음은 Resources 폴더에 리소스들을 넣어둔 뒤 그대로 빌드했을 때와, 폴더 이름을 변경한 후 일부 리소스만 Addressable로 등록한 후 빌드했을 때의 용량 차이입니다. 리소스 크기에 따라 이보다 훨씬 큰 차이가 날 수 있을 것으로 보입니다.
물론 Addressable 기능에도 단점은 있습니다. 우선 비동기 로딩 방식만을 지원하므로, 동기적으로 로딩하려면 추가적인 코드 작성이 필요합니다. 또한 로딩 완료 후 실행할 내용을 콜백함수로 전달하는데 람다식을 자주 사용하다보니 Resources 방식에 비해 코드가 다소 길고 복잡해지는 경향이 있습니다. 동기 방식으로 로딩해야 될 경우가 많거나, 예비 리소스가 많지 않아서 용량 낭비도 크지 않을 것으로 예상된다면 기존의 Resources 방식을 이용하는 것이 나을 수도 있습니다.
기존에 작업하던 포트폴리오 프로젝트들은 소규모인 만큼 그냥 프리팹으로 직접 등록해서 사용하는 방식을 많이 사용했는데, 실제 프로젝트에서는 리소스 양이 방대한 만큼 필요할 때 로드하는 방식을 많이 사용한다고 합니다. 이번에 Addressable이란 새로운 기능에 대해 알게되었으니, 기존의 리소스 사용 로직을 해당 기능을 이용하여 로드하는 방식으로 수정해보면서 연습해야겠습니다.
'Unity' 카테고리의 다른 글
[Unity][기타] 데이터 경로 관련 (0) | 2023.10.03 |
---|---|
[Unity] 클릭/터치 인터페이스(IPointerClickHandler, IPointerDownHandler, IPointerUpHandler) (1) | 2023.09.06 |
[Unity][포트폴리오] Rigidbody.AddForce()가 제대로 동작하지 않는 이슈 (0) | 2023.07.03 |
[Unity] 애니메이션 추가하기 (0) | 2023.07.01 |
[Unity][UI] Content Size Fitter (0) | 2023.04.13 |