사전 다운로드 가이드
앱인토스 Unity 게임에서 사전 다운로드 기능을 구현하여 게임 플레이 중 끊김 없는 경험을 제공하는 방법을 다뤄요.
1. 사전 다운로드 시스템 개요
사전 다운로드 전략
📥 사전 다운로드 아키텍처
├── 필수 콘텐츠 (게임 시작 전 다운로드)
│ ├── 핵심 게임 로직
│ ├── 기본 UI 리소스
│ ├── 첫 레벨 에셋
│ └── 필수 오디오
├── 우선순위 콘텐츠 (백그라운드 다운로드)
│ ├── 다음 레벨 에셋
│ ├── 캐릭터 스킨
│ ├── 추가 음악
│ └── 이펙트 에셋
├── 선택적 콘텐츠 (필요시 다운로드)
│ ├── 고해상도 텍스처
│ ├── 보너스 콘텐츠
│ ├── 계절 이벤트 에셋
│ └── 언어팩
└── 예측적 콘텐츠 (사용자 패턴 기반)
├── 자주 플레이하는 레벨
├── 선호 캐릭터 관련
├── 개인화 콘텐츠
└── 추천 에셋사전 다운로드 매니저
c#
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
public class AppsInTossPreloadManager : MonoBehaviour
{
public static AppsInTossPreloadManager Instance { get; private set; }
[System.Serializable]
public class PreloadItem
{
[Header("기본 정보")]
public string itemId;
public string itemName;
public string description;
public float sizeMB;
public string version = "1.0.0";
[Header("다운로드 설정")]
public PreloadPriority priority;
public PreloadTrigger downloadTrigger;
public string[] dependencies;
public string remoteUrl;
public string localPath;
[Header("조건 설정")]
public bool requiresWiFi = false;
public bool downloadOnlyWhenIdle = false;
public int minimumBatteryLevel = 20;
public long maxStorageUsageMB = 500;
[Header("게임 로직")]
public string[] requiredForScenes;
public string[] requiredForFeatures;
public bool isUserGenerated = false;
public System.DateTime expiryDate;
}
public enum PreloadPriority
{
Critical = 0, // 게임 진행에 필수
High = 1, // 사용자 경험 향상에 중요
Medium = 2, // 부가 기능
Low = 3, // 선택적 콘텐츠
OnDemand = 4 // 요청시에만 다운로드
}
public enum PreloadTrigger
{
Immediate, // 즉시 다운로드
OnGameStart, // 게임 시작시
OnSceneLoad, // 특정 씬 로드시
OnFeatureAccess, // 기능 접근시
OnUserRequest, // 사용자 요청시
OnWiFiAvailable, // WiFi 연결시
OnIdle, // 유휴 시간에
OnBatteryOK // 배터리 충분시
}
[Header("사전 다운로드 설정")]
public PreloadItem[] preloadItems;
[Header("네트워크 설정")]
public int maxConcurrentDownloads = 2;
public float downloadTimeoutSeconds = 30f;
public bool enableBackgroundDownload = true;
public bool enablePredictiveDownload = true;
[Header("저장소 관리")]
public long maxTotalCacheSizeMB = 1000;
public bool enableAutomaticCleanup = true;
public int keepRecentDays = 30;
// 내부 상태
private Dictionary<string, PreloadItem> itemMap = new Dictionary<string, PreloadItem>();
private HashSet<string> downloadedItems = new HashSet<string>();
private HashSet<string> downloadingItems = new HashSet<string>();
private Queue<PreloadItem> downloadQueue = new Queue<PreloadItem>();
private Dictionary<string, float> downloadProgress = new Dictionary<string, float>();
private int activeDownloads = 0;
private bool isInitialized = false;
// 사용자 패턴 분석
private Dictionary<string, int> usageFrequency = new Dictionary<string, int>();
private Dictionary<string, System.DateTime> lastUsedTime = new Dictionary<string, System.DateTime>();
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
InitializePreloadSystem();
}
else
{
Destroy(gameObject);
}
}
void InitializePreloadSystem()
{
// 아이템 맵 생성
foreach (var item in preloadItems)
{
itemMap[item.itemId] = item;
}
// 로컬 캐시 상태 확인
LoadCacheStatus();
// 사용 패턴 데이터 로드
LoadUsagePatterns();
// 시스템 상태 모니터링 시작
StartCoroutine(MonitorSystemConditions());
// 초기 다운로드 큐 설정
SetupInitialDownloadQueue();
isInitialized = true;
Debug.Log("AppsInToss 사전 다운로드 시스템 초기화 완료");
}
void LoadCacheStatus()
{
string cacheStatusPath = Path.Combine(Application.persistentDataPath, "preload_cache_status.json");
if (File.Exists(cacheStatusPath))
{
try
{
string json = File.ReadAllText(cacheStatusPath);
var cacheStatus = JsonUtility.FromJson<CacheStatus>(json);
downloadedItems = new HashSet<string>(cacheStatus.downloadedItems);
Debug.Log($"캐시 상태 로드 완료: {downloadedItems.Count}개 아이템");
}
catch (System.Exception e)
{
Debug.LogError($"캐시 상태 로드 실패: {e.Message}");
}
}
}
void SaveCacheStatus()
{
string cacheStatusPath = Path.Combine(Application.persistentDataPath, "preload_cache_status.json");
var cacheStatus = new CacheStatus
{
downloadedItems = downloadedItems.ToArray(),
lastUpdateTime = System.DateTime.UtcNow.ToString("o")
};
try
{
string json = JsonUtility.ToJson(cacheStatus, true);
File.WriteAllText(cacheStatusPath, json);
}
catch (System.Exception e)
{
Debug.LogError($"캐시 상태 저장 실패: {e.Message}");
}
}
void LoadUsagePatterns()
{
string patternsPath = Path.Combine(Application.persistentDataPath, "usage_patterns.json");
if (File.Exists(patternsPath))
{
try
{
string json = File.ReadAllText(patternsPath);
var patterns = JsonUtility.FromJson<UsagePatterns>(json);
foreach (var item in patterns.items)
{
usageFrequency[item.itemId] = item.frequency;
if (System.DateTime.TryParse(item.lastUsed, out System.DateTime lastUsed))
{
lastUsedTime[item.itemId] = lastUsed;
}
}
Debug.Log($"사용 패턴 로드 완료: {usageFrequency.Count}개 항목");
}
catch (System.Exception e)
{
Debug.LogError($"사용 패턴 로드 실패: {e.Message}");
}
}
}
void SaveUsagePatterns()
{
string patternsPath = Path.Combine(Application.persistentDataPath, "usage_patterns.json");
var patterns = new UsagePatterns();
patterns.items = new List<UsagePatternItem>();
foreach (var kvp in usageFrequency)
{
var item = new UsagePatternItem
{
itemId = kvp.Key,
frequency = kvp.Value,
lastUsed = lastUsedTime.ContainsKey(kvp.Key) ?
lastUsedTime[kvp.Key].ToString("o") :
System.DateTime.UtcNow.ToString("o")
};
patterns.items.Add(item);
}
try
{
string json = JsonUtility.ToJson(patterns, true);
File.WriteAllText(patternsPath, json);
}
catch (System.Exception e)
{
Debug.LogError($"사용 패턴 저장 실패: {e.Message}");
}
}
IEnumerator MonitorSystemConditions()
{
while (true)
{
// 네트워크 상태 확인
bool isWiFiAvailable = Application.internetReachability == NetworkReachability.ReachableViaLocalAreaNetwork;
// 배터리 상태 확인 (모바일 플랫폼에서만)
float batteryLevel = SystemInfo.batteryLevel * 100f;
// 저장 공간 확인
long availableStorage = GetAvailableStorageSpace();
// 시스템 조건에 따른 다운로드 제어
UpdateDownloadBehavior(isWiFiAvailable, batteryLevel, availableStorage);
// 다운로드 큐 처리
ProcessDownloadQueue();
yield return new WaitForSeconds(5f);
}
}
void UpdateDownloadBehavior(bool isWiFiAvailable, float batteryLevel, long availableStorage)
{
// WiFi 전용 아이템들 처리
if (isWiFiAvailable)
{
var wifiOnlyItems = preloadItems.Where(item =>
item.requiresWiFi &&
!downloadedItems.Contains(item.itemId) &&
!downloadingItems.Contains(item.itemId)
).ToArray();
foreach (var item in wifiOnlyItems)
{
if (ShouldDownloadItem(item, batteryLevel, availableStorage))
{
QueueItemForDownload(item);
}
}
}
// 배터리 조건 확인
if (batteryLevel < 20f)
{
// 배터리가 부족하면 중요하지 않은 다운로드 일시 정지
PauseNonCriticalDownloads();
}
// 저장 공간 부족시 정리
if (availableStorage < 100 * 1024 * 1024) // 100MB 미만
{
StartCoroutine(CleanupOldCache());
}
}
bool ShouldDownloadItem(PreloadItem item, float batteryLevel, long availableStorage)
{
// 배터리 레벨 체크
if (batteryLevel < item.minimumBatteryLevel)
{
return false;
}
// 저장 공간 체크
if (availableStorage < item.sizeMB * 1024 * 1024)
{
return false;
}
// 트리거 조건 체크
if (item.downloadTrigger == PreloadTrigger.OnWiFiAvailable &&
Application.internetReachability != NetworkReachability.ReachableViaLocalAreaNetwork)
{
return false;
}
// 유휴 시간 체크
if (item.downloadOnlyWhenIdle && !IsPlayerIdle())
{
return false;
}
return true;
}
void SetupInitialDownloadQueue()
{
// 우선순위별로 정렬
var sortedItems = preloadItems
.Where(item => item.downloadTrigger == PreloadTrigger.Immediate ||
item.downloadTrigger == PreloadTrigger.OnGameStart)
.OrderBy(item => item.priority)
.ThenByDescending(item => GetItemPredictionScore(item));
foreach (var item in sortedItems)
{
if (!downloadedItems.Contains(item.itemId))
{
QueueItemForDownload(item);
}
}
Debug.Log($"초기 다운로드 큐 설정 완료: {downloadQueue.Count}개 아이템");
}
float GetItemPredictionScore(PreloadItem item)
{
float score = 0f;
// 사용 빈도 점수
if (usageFrequency.ContainsKey(item.itemId))
{
score += usageFrequency[item.itemId] * 10f;
}
// 최근 사용 점수
if (lastUsedTime.ContainsKey(item.itemId))
{
var daysSinceLastUse = (System.DateTime.UtcNow - lastUsedTime[item.itemId]).TotalDays;
score += Mathf.Max(0, 30f - (float)daysSinceLastUse);
}
// 크기 점수 (작을수록 높은 점수)
score += Mathf.Max(0, 50f - item.sizeMB);
return score;
}
void ProcessDownloadQueue()
{
while (downloadQueue.Count > 0 && activeDownloads < maxConcurrentDownloads)
{
var nextItem = downloadQueue.Dequeue();
if (!downloadedItems.Contains(nextItem.itemId) &&
!downloadingItems.Contains(nextItem.itemId))
{
StartCoroutine(DownloadItem(nextItem));
}
}
}
IEnumerator DownloadItem(PreloadItem item)
{
downloadingItems.Add(item.itemId);
activeDownloads++;
downloadProgress[item.itemId] = 0f;
Debug.Log($"다운로드 시작: {item.itemName} ({item.sizeMB:F1}MB)");
float startTime = Time.realtimeSinceStartup;
// 의존성 체크 및 다운로드
yield return StartCoroutine(EnsureDependencies(item));
// 실제 파일 다운로드
string downloadUrl = item.remoteUrl;
string localPath = GetLocalPath(item);
using (var www = new WWW(downloadUrl))
{
float timeoutTime = Time.realtimeSinceStartup + downloadTimeoutSeconds;
while (!www.isDone && Time.realtimeSinceStartup < timeoutTime)
{
downloadProgress[item.itemId] = www.progress;
// 진행률 이벤트 발송
SendDownloadProgressEvent(item.itemId, www.progress);
yield return null;
}
if (www.isDone && string.IsNullOrEmpty(www.error))
{
// 파일 저장
Directory.CreateDirectory(Path.GetDirectoryName(localPath));
File.WriteAllBytes(localPath, www.bytes);
// 성공 처리
OnDownloadSuccess(item, Time.realtimeSinceStartup - startTime);
}
else
{
// 실패 처리
OnDownloadFailed(item, www.error);
}
}
downloadingItems.Remove(item.itemId);
activeDownloads--;
downloadProgress.Remove(item.itemId);
}
IEnumerator EnsureDependencies(PreloadItem item)
{
foreach (var dependencyId in item.dependencies)
{
if (!downloadedItems.Contains(dependencyId))
{
if (itemMap.ContainsKey(dependencyId))
{
var dependency = itemMap[dependencyId];
yield return StartCoroutine(DownloadItem(dependency));
}
}
}
}
void OnDownloadSuccess(PreloadItem item, float downloadTime)
{
downloadedItems.Add(item.itemId);
SaveCacheStatus();
Debug.Log($"다운로드 완료: {item.itemName} ({downloadTime:F2}초)");
// 성공 분석 데이터 전송
SendDownloadAnalytics(item, true, downloadTime, null);
// 완료 이벤트 발송
AppsInToss.SendEvent("preload_item_downloaded", new Dictionary<string, object>
{
{"item_id", item.itemId},
{"item_name", item.itemName},
{"size_mb", item.sizeMB},
{"download_time", downloadTime}
});
}
void OnDownloadFailed(PreloadItem item, string error)
{
Debug.LogError($"다운로드 실패: {item.itemName} - {error}");
// 실패 분석 데이터 전송
SendDownloadAnalytics(item, false, 0f, error);
// 재시도 로직 (낮은 우선순위 아이템만)
if (item.priority >= PreloadPriority.Medium)
{
StartCoroutine(RetryDownloadAfterDelay(item, 10f));
}
}
IEnumerator RetryDownloadAfterDelay(PreloadItem item, float delay)
{
yield return new WaitForSeconds(delay);
if (!downloadedItems.Contains(item.itemId))
{
QueueItemForDownload(item);
Debug.Log($"다운로드 재시도 예약: {item.itemName}");
}
}
void SendDownloadProgressEvent(string itemId, float progress)
{
AppsInToss.SendEvent("preload_progress", new Dictionary<string, object>
{
{"item_id", itemId},
{"progress", progress}
});
}
void SendDownloadAnalytics(PreloadItem item, bool success, float downloadTime, string error)
{
var analyticsData = new Dictionary<string, object>
{
{"item_id", item.itemId},
{"item_name", item.itemName},
{"size_mb", item.sizeMB},
{"priority", item.priority.ToString()},
{"success", success},
{"download_time", downloadTime},
{"error_message", error ?? ""},
{"network_type", Application.internetReachability.ToString()},
{"device_model", SystemInfo.deviceModel},
{"timestamp", System.DateTime.UtcNow.ToString("o")}
};
AppsInToss.SendAnalytics("preload_download", analyticsData);
}
string GetLocalPath(PreloadItem item)
{
if (!string.IsNullOrEmpty(item.localPath))
{
return Path.Combine(Application.persistentDataPath, item.localPath);
}
return Path.Combine(Application.persistentDataPath, "PreloadCache", item.itemId);
}
long GetAvailableStorageSpace()
{
// 플랫폼별 저장 공간 확인 로직
// 여기서는 간단한 추정값 반환
return 1024 * 1024 * 1024; // 1GB
}
bool IsPlayerIdle()
{
// 플레이어 유휴 상태 확인 로직
return Time.realtimeSinceStartup - Time.time > 30f;
}
void PauseNonCriticalDownloads()
{
// 현재 진행중인 중요하지 않은 다운로드들을 일시 정지
Debug.Log("배터리 부족으로 인한 비중요 다운로드 일시 정지");
}
IEnumerator CleanupOldCache()
{
Debug.Log("저장 공간 부족으로 인한 캐시 정리 시작");
var cacheDir = Path.Combine(Application.persistentDataPath, "PreloadCache");
if (Directory.Exists(cacheDir))
{
var files = Directory.GetFiles(cacheDir, "*", SearchOption.AllDirectories);
var fileInfos = files.Select(f => new FileInfo(f)).ToArray();
// 오래된 파일부터 정렬
System.Array.Sort(fileInfos, (a, b) => a.LastAccessTime.CompareTo(b.LastAccessTime));
long freedSpace = 0;
long targetFreeSpace = 200 * 1024 * 1024; // 200MB
foreach (var fileInfo in fileInfos)
{
if (freedSpace >= targetFreeSpace) break;
try
{
freedSpace += fileInfo.Length;
File.Delete(fileInfo.FullName);
// 해당 아이템을 다운로드 목록에서 제거
string itemId = Path.GetFileNameWithoutExtension(fileInfo.Name);
downloadedItems.Remove(itemId);
Debug.Log($"캐시 파일 삭제: {fileInfo.Name}");
}
catch (System.Exception e)
{
Debug.LogError($"캐시 파일 삭제 실패: {e.Message}");
}
yield return null;
}
SaveCacheStatus();
Debug.Log($"캐시 정리 완료: {freedSpace / (1024 * 1024)}MB 확보");
}
}
// 공개 API
public void QueueItemForDownload(PreloadItem item)
{
if (!downloadedItems.Contains(item.itemId) &&
!downloadingItems.Contains(item.itemId))
{
downloadQueue.Enqueue(item);
Debug.Log($"다운로드 큐에 추가: {item.itemName}");
}
}
public void QueueItemForDownload(string itemId)
{
if (itemMap.ContainsKey(itemId))
{
QueueItemForDownload(itemMap[itemId]);
}
}
public bool IsItemDownloaded(string itemId)
{
return downloadedItems.Contains(itemId);
}
public float GetDownloadProgress(string itemId)
{
return downloadProgress.ContainsKey(itemId) ? downloadProgress[itemId] : 0f;
}
public bool IsItemDownloading(string itemId)
{
return downloadingItems.Contains(itemId);
}
public void RecordItemUsage(string itemId)
{
if (usageFrequency.ContainsKey(itemId))
{
usageFrequency[itemId]++;
}
else
{
usageFrequency[itemId] = 1;
}
lastUsedTime[itemId] = System.DateTime.UtcNow;
// 주기적으로 패턴 저장
if (usageFrequency[itemId] % 10 == 0)
{
SaveUsagePatterns();
}
}
public long GetTotalCacheSize()
{
var cacheDir = Path.Combine(Application.persistentDataPath, "PreloadCache");
if (!Directory.Exists(cacheDir))
{
return 0;
}
long totalSize = 0;
var files = Directory.GetFiles(cacheDir, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
totalSize += new FileInfo(file).Length;
}
return totalSize;
}
public PreloadItem[] GetQueuedItems()
{
return downloadQueue.ToArray();
}
public string[] GetDownloadedItemIds()
{
return downloadedItems.ToArray();
}
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
// 앱 일시정지시 패턴 데이터 저장
SaveUsagePatterns();
}
}
void OnDestroy()
{
SaveCacheStatus();
SaveUsagePatterns();
}
}
// 데이터 클래스들
[System.Serializable]
public class CacheStatus
{
public string[] downloadedItems;
public string lastUpdateTime;
}
[System.Serializable]
public class UsagePatterns
{
public List<UsagePatternItem> items;
}
[System.Serializable]
public class UsagePatternItem
{
public string itemId;
public int frequency;
public string lastUsed;
}2. 사용자 인터페이스
다운로드 관리 UI
c#
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
using System.Linq;
public class PreloadManagerUI : MonoBehaviour
{
[Header("UI 컴포넌트")]
public GameObject downloadItemPrefab;
public Transform downloadItemContainer;
public Button pauseAllButton;
public Button resumeAllButton;
public Button clearCacheButton;
public TextMeshProUGUI totalSizeText;
public TextMeshProUGUI availableSpaceText;
public Slider totalProgressSlider;
[Header("설정 UI")]
public Toggle wifiOnlyToggle;
public Toggle backgroundDownloadToggle;
public Slider maxCacheSizeSlider;
public TextMeshProUGUI maxCacheSizeLabel;
private Dictionary<string, GameObject> downloadItemUIs = new Dictionary<string, GameObject>();
private bool isPaused = false;
void Start()
{
SetupUI();
RegisterEventListeners();
UpdateUI();
}
void SetupUI()
{
// 버튼 리스너 등록
pauseAllButton.onClick.AddListener(PauseAllDownloads);
resumeAllButton.onClick.AddListener(ResumeAllDownloads);
clearCacheButton.onClick.AddListener(ClearCache);
// 토글 리스너 등록
wifiOnlyToggle.onValueChanged.AddListener(OnWiFiOnlyChanged);
backgroundDownloadToggle.onValueChanged.AddListener(OnBackgroundDownloadChanged);
// 슬라이더 리스너 등록
maxCacheSizeSlider.onValueChanged.AddListener(OnMaxCacheSizeChanged);
// 초기 설정 로드
LoadUserPreferences();
}
void RegisterEventListeners()
{
AppsInToss.OnEvent += HandlePreloadEvent;
}
void HandlePreloadEvent(string eventName, Dictionary<string, object> data)
{
switch (eventName)
{
case "preload_item_downloaded":
string itemId = data["item_id"] as string;
UpdateDownloadItemUI(itemId, 1f, "완료");
break;
case "preload_progress":
itemId = data["item_id"] as string;
float progress = (float)data["progress"];
UpdateDownloadItemUI(itemId, progress, $"{progress * 100f:F0}%");
break;
}
UpdateUI();
}
void UpdateUI()
{
if (AppsInTossPreloadManager.Instance == null) return;
// 전체 캐시 크기 표시
long totalCacheSize = AppsInTossPreloadManager.Instance.GetTotalCacheSize();
totalSizeText.text = $"사용 중: {FormatFileSize(totalCacheSize)}";
// 사용 가능한 공간 표시 (추정값)
availableSpaceText.text = "사용 가능: ~1.2GB";
// 전체 진행률 계산
UpdateTotalProgress();
// 개별 다운로드 아이템 UI 업데이트
UpdateDownloadItemsUI();
}
void UpdateTotalProgress()
{
var queuedItems = AppsInTossPreloadManager.Instance.GetQueuedItems();
var downloadedItems = AppsInTossPreloadManager.Instance.GetDownloadedItemIds();
if (queuedItems.Length == 0)
{
totalProgressSlider.value = 1f;
return;
}
float totalItems = queuedItems.Length + downloadedItems.Length;
float completedItems = downloadedItems.Length;
// 현재 다운로드 중인 아이템들의 부분 진행률 추가
foreach (var item in queuedItems)
{
if (AppsInTossPreloadManager.Instance.IsItemDownloading(item.itemId))
{
float itemProgress = AppsInTossPreloadManager.Instance.GetDownloadProgress(item.itemId);
completedItems += itemProgress;
}
}
totalProgressSlider.value = totalItems > 0 ? completedItems / totalItems : 1f;
}
void UpdateDownloadItemsUI()
{
var queuedItems = AppsInTossPreloadManager.Instance.GetQueuedItems();
var downloadedItems = AppsInTossPreloadManager.Instance.GetDownloadedItemIds();
// 큐에 있는 아이템들 UI 생성/업데이트
foreach (var item in queuedItems)
{
if (!downloadItemUIs.ContainsKey(item.itemId))
{
CreateDownloadItemUI(item);
}
bool isDownloading = AppsInTossPreloadManager.Instance.IsItemDownloading(item.itemId);
float progress = isDownloading ?
AppsInTossPreloadManager.Instance.GetDownloadProgress(item.itemId) : 0f;
string status = isDownloading ? "다운로드 중" : "대기 중";
UpdateDownloadItemUI(item.itemId, progress, status);
}
// 완료된 아이템들 처리
foreach (var itemId in downloadedItems)
{
if (downloadItemUIs.ContainsKey(itemId))
{
UpdateDownloadItemUI(itemId, 1f, "완료");
}
}
}
void CreateDownloadItemUI(AppsInTossPreloadManager.PreloadItem item)
{
var itemUI = Instantiate(downloadItemPrefab, downloadItemContainer);
downloadItemUIs[item.itemId] = itemUI;
// UI 컴포넌트 설정
var nameText = itemUI.transform.Find("NameText").GetComponent<TextMeshProUGUI>();
var sizeText = itemUI.transform.Find("SizeText").GetComponent<TextMeshProUGUI>();
var progressSlider = itemUI.transform.Find("ProgressSlider").GetComponent<Slider>();
var statusText = itemUI.transform.Find("StatusText").GetComponent<TextMeshProUGUI>();
var cancelButton = itemUI.transform.Find("CancelButton").GetComponent<Button>();
nameText.text = item.itemName;
sizeText.text = FormatFileSize((long)(item.sizeMB * 1024 * 1024));
progressSlider.value = 0f;
statusText.text = "대기 중";
// 취소 버튼 이벤트
cancelButton.onClick.AddListener(() => CancelDownload(item.itemId));
// 우선순위에 따른 색상 설정
var priorityIcon = itemUI.transform.Find("PriorityIcon").GetComponent<Image>();
priorityIcon.color = GetPriorityColor(item.priority);
}
void UpdateDownloadItemUI(string itemId, float progress, string status)
{
if (!downloadItemUIs.ContainsKey(itemId)) return;
var itemUI = downloadItemUIs[itemId];
var progressSlider = itemUI.transform.Find("ProgressSlider").GetComponent<Slider>();
var statusText = itemUI.transform.Find("StatusText").GetComponent<TextMeshProUGUI>();
progressSlider.value = progress;
statusText.text = status;
// 완료된 아이템은 일정 시간 후 제거
if (progress >= 1f)
{
StartCoroutine(RemoveCompletedItemUI(itemId, 3f));
}
}
System.Collections.IEnumerator RemoveCompletedItemUI(string itemId, float delay)
{
yield return new WaitForSeconds(delay);
if (downloadItemUIs.ContainsKey(itemId))
{
Destroy(downloadItemUIs[itemId]);
downloadItemUIs.Remove(itemId);
}
}
Color GetPriorityColor(AppsInTossPreloadManager.PreloadPriority priority)
{
switch (priority)
{
case AppsInTossPreloadManager.PreloadPriority.Critical:
return Color.red;
case AppsInTossPreloadManager.PreloadPriority.High:
return Color.yellow;
case AppsInTossPreloadManager.PreloadPriority.Medium:
return Color.green;
case AppsInTossPreloadManager.PreloadPriority.Low:
return Color.gray;
default:
return Color.white;
}
}
string FormatFileSize(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:0.##} {sizes[order]}";
}
void PauseAllDownloads()
{
isPaused = true;
// 다운로드 일시정지 로직 구현
Debug.Log("모든 다운로드 일시정지");
}
void ResumeAllDownloads()
{
isPaused = false;
// 다운로드 재개 로직 구현
Debug.Log("모든 다운로드 재개");
}
void ClearCache()
{
// 캐시 정리 확인 다이얼로그 표시
ShowClearCacheDialog();
}
void ShowClearCacheDialog()
{
// 간단한 확인 다이얼로그
bool confirmed = true; // 실제로는 다이얼로그 결과
if (confirmed)
{
// 캐시 정리 실행
Debug.Log("캐시 정리 시작");
}
}
void CancelDownload(string itemId)
{
// 다운로드 취소 로직
if (downloadItemUIs.ContainsKey(itemId))
{
Destroy(downloadItemUIs[itemId]);
downloadItemUIs.Remove(itemId);
}
Debug.Log($"다운로드 취소: {itemId}");
}
void OnWiFiOnlyChanged(bool value)
{
// WiFi 전용 설정 변경
SaveUserPreference("wifi_only", value);
}
void OnBackgroundDownloadChanged(bool value)
{
// 백그라운드 다운로드 설정 변경
SaveUserPreference("background_download", value);
}
void OnMaxCacheSizeChanged(float value)
{
// 최대 캐시 크기 설정 변경
long maxSizeMB = (long)(value * 1000);
maxCacheSizeLabel.text = $"최대 캐시 크기: {maxSizeMB}MB";
SaveUserPreference("max_cache_size_mb", maxSizeMB);
}
void LoadUserPreferences()
{
wifiOnlyToggle.isOn = GetUserPreference("wifi_only", false);
backgroundDownloadToggle.isOn = GetUserPreference("background_download", true);
long maxCacheSizeMB = GetUserPreference("max_cache_size_mb", 500L);
maxCacheSizeSlider.value = maxCacheSizeMB / 1000f;
maxCacheSizeLabel.text = $"최대 캐시 크기: {maxCacheSizeMB}MB";
}
void SaveUserPreference(string key, object value)
{
// 사용자 설정 저장
PlayerPrefs.SetString($"preload_{key}", value.ToString());
PlayerPrefs.Save();
}
T GetUserPreference<T>(string key, T defaultValue)
{
// 사용자 설정 로드
string prefKey = $"preload_{key}";
if (PlayerPrefs.HasKey(prefKey))
{
string value = PlayerPrefs.GetString(prefKey);
try
{
return (T)System.Convert.ChangeType(value, typeof(T));
}
catch
{
return defaultValue;
}
}
return defaultValue;
}
void Update()
{
// UI 주기적 업데이트
if (Time.frameCount % 60 == 0) // 1초마다
{
UpdateUI();
}
}
void OnDestroy()
{
AppsInToss.OnEvent -= HandlePreloadEvent;
}
}사전 다운로드를 통해 게임 플레이 중 끊김을 최소화하되, 사용자의 데이터 요금과 배터리, 저장 공간을 고려한 스마트한 다운로드 전략을 구현하세요.
