
📌 글 작성 이유
보통 모바일 게임을 다운로드 받은 뒤에 그림과 같이 리소스 파일을 다운로드 받는데 나 역시도 실제 프로젝트에 적용을 한 적이 있어서 기록하고자 글을 작성해 본다.
전체 소스 코드
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class DownManager : MonoBehaviour
{
[Header("UI")]
public GameObject waitMessage; // 대기 메시지 UI
public GameObject downMessage; // 다운로드 메시지 UI
public Scrollbar downSlider; // 다운로드 진행 상태를 표시하는 슬라이더
public Text sizeInfoText; // 다운로드할 파일 크기 정보 텍스트
public Text downValText; // 다운로드 진행률 텍스트
[Header("Label")]
public AssetLabelReference player; // 플레이어 관련 어드레서블 레이블
public AssetLabelReference selecter; // 선택자 관련 어드레서블 레이블
public AssetLabelReference items; // 아이템 관련 어드레서블 레이블
public AssetLabelReference enemy; // 적 관련 어드레서블 레이블
public AssetLabelReference sprite; // 스프라이트 관련 어드레서블 레이블
public AssetLabelReference mymonster; // 몬스터 관련 어드레서블 레이블
private long patchSize; // 다운로드할 파일의 총 크기
private Dictionary<string, long> patchMap = new Dictionary<string, long>(); // 각 라벨에 대한 다운로드 진행 상태 추적
void Start()
{
waitMessage.SetActive(true); // 대기 메시지를 표시
downMessage.SetActive(false); // 다운로드 메시지는 숨김
// 어드레서블 리소스 초기화
StartCoroutine(InitAddressable());
// 다운로드할 파일이 있는지 체크
StartCoroutine(CheckUpdateFiles());
}
// 어드레서블 초기화 코루틴
IEnumerator InitAddressable()
{
var init = Addressables.InitializeAsync(); // 어드레서블 시스템 비동기 초기화
yield return init; // 초기화가 완료될 때까지 대기
}
// 다운로드할 파일이 있는지 체크하는 코루틴
IEnumerator CheckUpdateFiles()
{
var labels = new List<string>() { player.labelString, selecter.labelString, items.labelString, enemy.labelString, sprite.labelString, mymonster.labelString };
patchSize = default; // 총 다운로드 크기 초기화
foreach (var label in labels)
{
var handle = Addressables.GetDownloadSizeAsync(label); // 각 라벨에 대해 다운로드 크기 확인
yield return handle; // 비동기 작업이 완료될 때까지 대기
patchSize += handle.Result; // 다운로드할 파일 크기 합산
}
if (patchSize > decimal.Zero)
{
waitMessage.SetActive(false); // 업데이트 체크 중 팝업 닫기
downMessage.SetActive(true); // 다운로드 팝업 열기
Debug.Log("[2 DownManager : 서버에서 다운로드 해야 할 리소스 파일 확인 됨]");
sizeInfoText.text = GetFileSize(patchSize); // 다운로드할 크기 UI에 표시
}
else // 다운로드할 파일이 없으면 씬을 변경
{
downValText.text = "100 %"; // 진행률을 100%로 설정
downSlider.size = 1f; // 슬라이더를 100%로 설정
yield return new WaitForSeconds(2f); // 2초 대기
Debug.Log("[2 LobbyManager : 다운로드 할 리소스 파일 없음]");
LoadingManager.LoadScene("4Login"); // 씬 전환
}
}
// 바이트 단위로 파일 크기를 사람이 읽을 수 있는 형식으로 변환
private string GetFileSize(long byteCnt)
{
string size = "0 Bytes";
if (byteCnt >= 1073741824.0)
{
size = string.Format("{0:##.##}", byteCnt / 1073741824.0 + " GB");
}
else if (byteCnt >= 1048576.0)
{
size = string.Format("{0:##.##}", byteCnt / 1048576.0 + " MB");
}
else if (byteCnt >= 1024.0)
{
size = string.Format("{0:##.##}", byteCnt / 1024.0 + " KB");
}
else if (byteCnt > 0 && byteCnt < 1024.0)
{
size = byteCnt.ToString() + " Bytes";
}
return size; // 사람이 읽을 수 있는 형식으로 파일 크기 반환
}
// 다운로드 버튼 클릭 시 호출되는 함수
public void Button_DownLoad()
{
StartCoroutine(PatchFiles()); // 파일 다운로드 시작
}
// 다운로드 파일을 패치하는 코루틴
IEnumerator PatchFiles()
{
var labels = new List<string>() { player.labelString, selecter.labelString, items.labelString, enemy.labelString, sprite.labelString, mymonster.labelString };
// 각 라벨에 대해 다운로드할 크기 확인
foreach (var label in labels)
{
var handle = Addressables.GetDownloadSizeAsync(label); // 라벨에 대해 다운로드 크기 확인
yield return handle; // 비동기 작업이 완료될 때까지 대기
if (handle.Result != decimal.Zero)
{
StartCoroutine(DownLoadLabel(label)); // 다운로드가 필요한 파일에 대해 다운로드 시작
}
}
yield return CheckDownLoad(); // 다운로드 진행 상태 체크
}
// 각 라벨에 대해 파일을 다운로드하는 코루틴
IEnumerator DownLoadLabel(string label)
{
patchMap.Add(label, 0); // 다운로드 진행 상태 초기화
var handle = Addressables.DownloadDependenciesAsync(label, false); // 해당 라벨의 의존성 파일 다운로드 시작 (리소스를 자동으로 로드하지 않음)
// 다운로드 진행 상태 추적
while (!handle.IsDone)
{
patchMap[label] = handle.GetDownloadStatus().DownloadedBytes; // 다운로드된 바이트 수 기록
yield return new WaitForEndOfFrame(); // 한 프레임 대기
}
patchMap[label] = handle.GetDownloadStatus().TotalBytes; // 다운로드 완료된 파일 크기 기록
Addressables.Release(handle); // 다운로드 완료 후 핸들 해제
}
// 다운로드 진행 상태를 확인하고 업데이트하는 코루틴
IEnumerator CheckDownLoad()
{
var total = 0f; // 다운로드된 파일의 총 크기
downValText.text = "0 %"; // 초기 진행률 표시
while (true)
{
total = patchMap.Sum(tmp => tmp.Value); // 다운로드된 총 크기 계산
downSlider.size = total / patchSize; // 슬라이더에 다운로드 진행 상태 반영
downValText.text = (int)(downSlider.size * 100) + " %"; // 텍스트로 진행률 표시
if (total >= patchSize)
{
Debug.Log("[2-1 다운 완료]"); // 다운로드 완료 로그
// 다운로드 완료 후 씬을 비동기적으로 로드
yield return StartCoroutine(LoadSceneAsync("4Login"));
break;
}
yield return new WaitForEndOfFrame(); // 한 프레임 대기
}
}
// 씬을 비동기적으로 로드하는 코루틴
IEnumerator LoadSceneAsync(string sceneName)
{
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); // 씬 비동기 로드
while (!asyncLoad.isDone) // 씬 로드가 완료될 때까지 대기
{
yield return null;
}
}
}
📌 순서
1. 초기화 Addressables.initializeAsync();

설명
어드레서블 시스템을 준비하기 위해 Addressables.InitializeAsync() 함수를 통해 비동기로 초기화를 진행한다.
- 어드레서블을 사용하기 위해 해줘야 하는 선행 작업이다.
- 핵심 코드 : Addressables.InitializeAsync()
- 비동기로 어드레서블 시스템을 초기화한다.
- 이 코드는 Unity의 어드레서블 API에서 제공하는 일부 기능이다.
2. 다운로드 되어야 할 파일 크기 체크 하기 Addressables.GetDownloadSizeAsync()

설명
CheckUpdateFiles 코루틴에서는 파일을 직접 다운로드 하지 않고 다운로드 받아야 하는 파일들의 총 크기를 체크 한다.

어떤 파일들의 크기를 체크 해야할까? 내가 어드레서블에서 라벨별로 묶어놓은 것들이 있다.
위 그림을 보면 몬스터 에너미 플레이어 등등 카테고리로 묶은 것들이 보이는데 이 라벨들을 모두 체크해야 한다.

우선 코드상에서 위와 같이 AssetLabelReference 클래스를 사용하면

위와 같이 특정 라벨을 선택할 수 있고

labelstring 속성을 사용하면 위 그림과 같이 스트랭 형태의 리스트에 라벨을 담을 수 있다.
이렇게 리스트에 담은 것들은 파일 사이즈 체크가 필요한 라벨들 입니다 라고 정의?하는 것과 같다.
자 이제 실제 파일 크기를 측정해 보자.

다운로드 해야 할 파일의 사이즈를 체크 하기 위해 각각의 라벨을 etDownloadSizeAsync 인자로 전달한다.
foreach에 의해 하나씩 돌면서 handle 에 반환 되는데 0을 반환하거나 실제 다운로드 크기를 반환한다. 반환 케이스는 아래와 같다
- 로컬에 리소스가 있으면: GetDownloadSizeAsync는 0을 반환.
- 서버에만 리소스가 있으면: GetDownloadSizeAsync는 해당 리소스의 다운로드 크기를 반환.
- 리소스가 업데이트된 경우: 서버의 리소스 크기를 반환하여 업데이트된 리소스를 다운로드하게 만듬.

실제 내 로컬로 확인을 해보자.
LocalLow -> Unity -> (프로젝트 명) 경로로 가면 아래와 같이 데이터들이 있는 것을 볼 수 있다 이미 나는 파일을 다운로드 받은 상태이다.

이 상태에서 handle.result를 Tostring 하여 확인해 봤더니 이미 로컬에 파일이 있기 때문에 0을 반환 하였다.

이번에는 아래와 같이 파일을 지워보았다.

서버에만 파일이 있기 때문에 파일 크기를 반환하였다.

결국 foreach를 모두 돌고 나면 patchSize에 다운받을 크기가 정해지고.만약 0 보다 크다면 로컬에 파일이 없거나 새롭게 업데이트 된 파일이 있는 것이니 위와 같이 if문을 타게 된다 반대로 0이라면 파일이 있기 때문에 바로 다른 씬으로 이동하고

if문을 탔다면 GetfileSize를 통해 주어진 바이트 크기를 사람이 읽기 쉬운 형식으로 변환한다 (예: 1.5 GB, 500 MB, 등).

그리고 나서 UI 표시하면 실제로 내가 다운로드 받아야 할 파일의 크기가 위 그림처럼 화면에 표시 된다.

다운로드 버튼 클릭 시 PatchFiles 코루틴이 실행 된다.
3. 파일 다운로드를 요청한 후 현재 다운로드 상태를 체크 한다.

PatchFiles 코루틴에서는 아래 세 가지 목적을 위한 로직이 작성되어 있다.
1. 파일 사이즈 체크 : GetDownloadSizeAsync 부분으로 파일 사이즈를 체크한다.
2. 파일 다운로드 요청 : DownLoadLabel 코루틴으로 라벨값을 넘겨서 실제 다운로드를 진행한다

보면 patchMap 은 딕셔너리로 되어있음을 볼 수 있는데 라벨 이름이 키가 되기 때문에 각 라벨별로 다운로드 크기값을 저장할 수 있다 그니까 파일 다운로드를 정확히 잘 했는지를 체크하기 위해 딕셔너리에 수치를 기록하는 것이다.

3. 현재 다운로드 상태 체크
PatchFile 코루틴 안에서 Foreach 문이 끝났다는 것은 파일 다운로드 요청 처리를 라벨별로 완료했다는 의미이며 이제 다운로드가 다 되었는지 while문 안에서 patchmap을 토대로 체크해서 퍼센트로 환산한 후 파일 다운로드가 완료 되었다면 씬을 변경하도록 했다.

정리
어드레서블 초기화를 진행한다.
어드레서블 라벨로 설정한 에셋들이 실제 내 로컬에 있는지, 리소스 변동사항이 서버에서 없는지 등을 체크해서 실제 내가 다운로드 받아야 하는 파일의 크기를 체크해 UI에 표시한다. 파일 다운로드 버튼을 누르면 다시 한 번 다운로드 받아야 하는 파일의 크기를 체크하고 크기가 0 이상인 라벨은 다운로드를 요청한다. foreach 문이 다 돌면 일단 다운로드 요청을 완료한 것이므로 patchMap 딕셔너리를 통해 현재 다운로드률을 while에서 실시간 체크하면서 퍼센트를 채워 나간다..
'유니티 > 구현내용정리' 카테고리의 다른 글
| 유니티 방치형 프로젝트 - 몬스터를 지정 반경 내에 랜덤하게 스폰 시키기(Random.insideUnitSphere) (0) | 2025.03.11 |
|---|---|
| 유니티 Robots 프로젝트 - 매치메이킹 구현 #1 (2) | 2025.03.04 |
| 유니티 데이터 드리븐 구조를 사용해서 게임 로직과 데이터를 분리하기 (0) | 2025.01.20 |
| 유니티 옵저버 패턴 정리 (디자인 패턴) (0) | 2025.01.19 |
| 유니티 MVC 패턴 특징 정리 (디자인 패턴) (0) | 2025.01.19 |
