앱인토스 개발자센터 로고
Skip to content

AutoStreaming 가이드

1. AutoStreaming 개요

AutoStreaming이란?

🚀 AutoStreaming 개념
├── 즉시 시작 (Instant Play) - 0-5초 내 게임 시작
├── 백그라운드 로딩 - 플레이 중 추가 콘텐츠 다운로드
├── 우선순위 기반 로딩 - 중요한 에셋 우선 로딩
└── 적응형 품질 - 네트워크 상태에 따른 품질 조절

AutoStreaming 아키텍처

c#
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;

public class AppsInTossAutoStreaming : MonoBehaviour
{
    public static AppsInTossAutoStreaming Instance { get; private set; }
    
    [System.Serializable]
    public class StreamingConfig
    {
        public string assetName;
        public StreamingPriority priority;
        public float sizeMB;
        public string[] dependencies;
        public bool essential = false; // 게임 진행에 필수 여부
        public int loadOrder = 0;
        public float timeoutSeconds = 30f;
    }
    
    public enum StreamingPriority
    {
        Immediate = 0,     // 즉시 로딩 (게임 시작 전 필수)
        High = 1,          // 게임 시작 후 바로 로딩
        Medium = 2,        // 사용자가 해당 영역에 접근하기 전 로딩
        Low = 3,           // 필요할 때만 로딩
        Preload = 4        // 여유가 있을 때 미리 로딩
    }
    
    [Header("스트리밍 설정")]
    public StreamingConfig[] streamingAssets;
    public float initialLoadThresholdMB = 2f; // 즉시 시작을 위한 초기 로딩 임계값
    public bool enableAdaptiveQuality = true;
    public bool enablePreloadInIdle = true;
    
    [Header("네트워크 설정")]
    public string baseUrl = "https://cdn.appintoss.com/streaming/";
    public int maxConcurrentDownloads = 3;
    public float networkTimeoutSeconds = 15f;
    
    // 내부 상태
    private Dictionary<string, StreamingConfig> configMap = new Dictionary<string, StreamingConfig>();
    private HashSet<string> loadedAssets = new HashSet<string>();
    private HashSet<string> downloadingAssets = new HashSet<string>();
    private Queue<StreamingConfig> downloadQueue = new Queue<StreamingConfig>();
    private bool isInitialLoadComplete = false;
    private int activeDownloads = 0;
    
    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeAutoStreaming();
        }
        else
        {
            Destroy(gameObject);
        }
    }
    
    void InitializeAutoStreaming()
    {
        // 설정 맵 생성
        foreach (var config in streamingAssets)
        {
            configMap[config.assetName] = config;
        }
        
        // 네트워크 상태 모니터링 시작
        StartCoroutine(MonitorNetworkStatus());
        
        // 즉시 로딩 가능한 최소한의 에셋 로딩
        StartCoroutine(LoadImmediateAssets());
    }
    
    IEnumerator LoadImmediateAssets()
    {
        Debug.Log("AutoStreaming 즉시 로딩 시작");
        float startTime = Time.realtimeSinceStartup;
        
        // Immediate 우선순위 에셋들을 크기 기준으로 정렬
        var immediateAssets = System.Array.FindAll(streamingAssets, 
            a => a.priority == StreamingPriority.Immediate);
        System.Array.Sort(immediateAssets, (a, b) => a.sizeMB.CompareTo(b.sizeMB));
        
        float loadedSizeMB = 0f;
        
        foreach (var asset in immediateAssets)
        {
            if (loadedSizeMB >= initialLoadThresholdMB && !asset.essential)
            {
                // 임계값 도달, 필수 에셋이 아니면 나중에 로딩
                QueueAssetForDownload(asset);
                continue;
            }
            
            yield return StartCoroutine(LoadAssetCoroutine(asset));
            loadedSizeMB += asset.sizeMB;
        }
        
        float loadTime = Time.realtimeSinceStartup - startTime;
        Debug.Log($"즉시 로딩 완료: {loadTime:F2}초, {loadedSizeMB:F1}MB 로딩됨");
        
        isInitialLoadComplete = true;
        
        // 게임 시작 가능 신호 전송
        SendGameReadySignal(loadTime, loadedSizeMB);
        
        // 백그라운드에서 나머지 에셋 로딩 시작
        StartCoroutine(BackgroundStreamingProcess());
    }
    
    IEnumerator LoadAssetCoroutine(StreamingConfig config)
    {
        if (loadedAssets.Contains(config.assetName))
        {
            yield break;
        }
        
        downloadingAssets.Add(config.assetName);
        
        // 의존성 에셋 먼저 로딩
        foreach (var dependency in config.dependencies)
        {
            if (!loadedAssets.Contains(dependency))
            {
                var dependencyConfig = configMap[dependency];
                yield return StartCoroutine(LoadAssetCoroutine(dependencyConfig));
            }
        }
        
        // 에셋 다운로드
        yield return StartCoroutine(DownloadAsset(config));
        
        downloadingAssets.Remove(config.assetName);
        loadedAssets.Add(config.assetName);
    }
    
    IEnumerator DownloadAsset(StreamingConfig config)
    {
        string assetUrl = baseUrl + config.assetName;
        string cachePath = GetAssetCachePath(config.assetName);
        
        Debug.Log($"에셋 스트리밍 시작: {config.assetName} ({config.sizeMB:F1}MB)");
        float startTime = Time.realtimeSinceStartup;
        
        // 로컬 캐시 확인
        if (File.Exists(cachePath))
        {
            yield return StartCoroutine(LoadFromCache(config, cachePath));
            yield break;
        }
        
        // 네트워크 다운로드
        using (var www = new WWW(assetUrl))
        {
            float timeoutTime = Time.realtimeSinceStartup + config.timeoutSeconds;
            
            while (!www.isDone && Time.realtimeSinceStartup < timeoutTime)
            {
                // 진행률 업데이트
                UpdateDownloadProgress(config.assetName, www.progress);
                yield return null;
            }
            
            if (www.isDone && string.IsNullOrEmpty(www.error))
            {
                // 캐시에 저장
                Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
                File.WriteAllBytes(cachePath, www.bytes);
                
                float downloadTime = Time.realtimeSinceStartup - startTime;
                Debug.Log($"스트리밍 완료: {config.assetName} ({downloadTime:F2}초)");
                
                SendStreamingAnalytics(config.assetName, true, downloadTime, config.sizeMB);
            }
            else
            {
                Debug.LogError($"스트리밍 실패: {config.assetName} - {www.error}");
                SendStreamingAnalytics(config.assetName, false, 0, config.sizeMB);
            }
        }
    }
    
    IEnumerator LoadFromCache(StreamingConfig config, string cachePath)
    {
        Debug.Log($"캐시에서 로딩: {config.assetName}");
        
        // 캐시 파일 검증
        var fileInfo = new FileInfo(cachePath);
        float expectedSizeMB = config.sizeMB;
        float actualSizeMB = fileInfo.Length / (1024f * 1024f);
        
        if (Mathf.Abs(actualSizeMB - expectedSizeMB) > 0.1f)
        {
            Debug.LogWarning($"캐시 파일 크기 불일치: {config.assetName} (예상: {expectedSizeMB:F1}MB, 실제: {actualSizeMB:F1}MB)");
            File.Delete(cachePath);
            
            // 다시 다운로드
            yield return StartCoroutine(DownloadAsset(config));
            yield break;
        }
        
        yield return null; // 1프레임 대기
        Debug.Log($"캐시 로딩 완료: {config.assetName}");
    }
    
    IEnumerator BackgroundStreamingProcess()
    {
        while (true)
        {
            // 큐에서 다음 에셋 처리
            if (downloadQueue.Count > 0 && activeDownloads < maxConcurrentDownloads)
            {
                var nextAsset = downloadQueue.Dequeue();
                StartCoroutine(BackgroundDownload(nextAsset));
            }
            
            // Idle 상태에서 preload 에셋 로딩
            if (enablePreloadInIdle && IsPlayerIdle() && activeDownloads == 0)
            {
                var preloadAssets = System.Array.FindAll(streamingAssets,
                    a => a.priority == StreamingPriority.Preload && !loadedAssets.Contains(a.assetName));
                
                if (preloadAssets.Length > 0)
                {
                    var nextPreload = preloadAssets[0];
                    QueueAssetForDownload(nextPreload);
                }
            }
            
            yield return new WaitForSeconds(1f);
        }
    }
    
    IEnumerator BackgroundDownload(StreamingConfig config)
    {
        activeDownloads++;
        yield return StartCoroutine(LoadAssetCoroutine(config));
        activeDownloads--;
    }
    
    void QueueAssetForDownload(StreamingConfig config)
    {
        if (!downloadingAssets.Contains(config.assetName) && 
            !loadedAssets.Contains(config.assetName))
        {
            downloadQueue.Enqueue(config);
        }
    }
    
    IEnumerator MonitorNetworkStatus()
    {
        while (true)
        {
            // 네트워크 상태 확인
            var networkReachability = Application.internetReachability;
            
            if (enableAdaptiveQuality)
            {
                AdaptQualityBasedOnNetwork(networkReachability);
            }
            
            yield return new WaitForSeconds(5f);
        }
    }
    
    void AdaptQualityBasedOnNetwork(NetworkReachability reachability)
    {
        switch (reachability)
        {
            case NetworkReachability.ReachableViaCarrierDataNetwork:
                // 모바일 데이터: 압축률 높이고 다운로드 제한
                maxConcurrentDownloads = 1;
                networkTimeoutSeconds = 30f;
                break;
                
            case NetworkReachability.ReachableViaLocalAreaNetwork:
                // WiFi: 최적 성능
                maxConcurrentDownloads = 3;
                networkTimeoutSeconds = 15f;
                break;
                
            case NetworkReachability.NotReachable:
                // 오프라인: 캐시된 에셋만 사용
                maxConcurrentDownloads = 0;
                break;
        }
    }
    
    string GetAssetCachePath(string assetName)
    {
        return Path.Combine(Application.persistentDataPath, "StreamingCache", assetName);
    }
    
    bool IsPlayerIdle()
    {
        // 플레이어가 유휴 상태인지 확인하는 로직
        // 예: 일정 시간 동안 입력이 없었는지, 메뉴 화면에 있는지 등
        return Time.realtimeSinceStartup - Time.time > 10f;
    }
    
    void UpdateDownloadProgress(string assetName, float progress)
    {
        // UI에 다운로드 진행률 업데이트
        var progressData = new Dictionary<string, object>
        {
            {"asset_name", assetName},
            {"progress", progress}
        };
        
        // 이벤트 시스템으로 UI에 알림
        AppsInToss.SendEvent("streaming_progress", progressData);
    }
    
    void SendGameReadySignal(float loadTime, float loadedSizeMB)
    {
        var readyData = new Dictionary<string, object>
        {
            {"load_time", loadTime},
            {"loaded_size_mb", loadedSizeMB},
            {"timestamp", System.DateTime.UtcNow.ToString("o")}
        };
        
        AppsInToss.SendEvent("game_ready", readyData);
        Debug.Log($"게임 시작 준비 완료! 로딩 시간: {loadTime:F2}초");
    }
    
    void SendStreamingAnalytics(string assetName, bool success, float downloadTime, float sizeMB)
    {
        var analyticsData = new Dictionary<string, object>
        {
            {"asset_name", assetName},
            {"success", success},
            {"download_time", downloadTime},
            {"size_mb", sizeMB},
            {"network_type", Application.internetReachability.ToString()},
            {"timestamp", System.DateTime.UtcNow.ToString("o")}
        };
        
        AppsInToss.SendAnalytics("streaming_performance", analyticsData);
    }
    
    // 공개 API
    public bool IsAssetLoaded(string assetName)
    {
        return loadedAssets.Contains(assetName);
    }
    
    public bool IsInitialLoadComplete()
    {
        return isInitialLoadComplete;
    }
    
    public void RequestAssetLoad(string assetName, StreamingPriority priority = StreamingPriority.High)
    {
        if (configMap.ContainsKey(assetName) && !IsAssetLoaded(assetName))
        {
            var config = configMap[assetName];
            config.priority = priority; // 우선순위 업데이트
            
            QueueAssetForDownload(config);
        }
    }
    
    public float GetLoadingProgress()
    {
        if (streamingAssets.Length == 0) return 1f;
        
        float totalAssets = streamingAssets.Length;
        float loadedCount = loadedAssets.Count;
        
        return loadedCount / totalAssets;
    }
    
    public string[] GetLoadedAssets()
    {
        var result = new string[loadedAssets.Count];
        loadedAssets.CopyTo(result);
        return result;
    }
    
    public long GetCacheSize()
    {
        string cacheDir = Path.Combine(Application.persistentDataPath, "StreamingCache");
        
        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 void ClearCache()
    {
        string cacheDir = Path.Combine(Application.persistentDataPath, "StreamingCache");
        
        if (Directory.Exists(cacheDir))
        {
            Directory.Delete(cacheDir, true);
            Debug.Log("스트리밍 캐시 정리 완료");
        }
        
        loadedAssets.Clear();
    }
}

2. 최적화 전략

에셋 우선순위 설정

c#
[System.Serializable]
public class AssetPriorityManager
{
    [Header("게임 시작 필수 에셋")]
    public string[] immediateAssets = {
        "core_ui",           // 핵심 UI 시스템
        "player_controller", // 플레이어 조작
        "first_level_mesh",  // 첫 번째 레벨 메시
        "essential_sounds"   // 핵심 사운드
    };
    
    [Header("조기 로딩 권장 에셋")]
    public string[] highPriorityAssets = {
        "level_backgrounds", // 레벨 배경
        "enemy_sprites",     // 적 스프라이트
        "effect_particles",  // 이펙트 파티클
        "ui_animations"      // UI 애니메이션
    };
    
    [Header("지연 로딩 가능 에셋")]
    public string[] lowPriorityAssets = {
        "cutscene_videos",   // 컷신 비디오
        "boss_models",       // 보스 모델
        "extra_music",       // 추가 음악
        "optional_effects"   // 선택적 이펙트
    };
    
    public StreamingPriority GetAssetPriority(string assetName)
    {
        if (System.Array.Exists(immediateAssets, a => a == assetName))
            return StreamingPriority.Immediate;
        else if (System.Array.Exists(highPriorityAssets, a => a == assetName))
            return StreamingPriority.High;
        else if (System.Array.Exists(lowPriorityAssets, a => a == assetName))
            return StreamingPriority.Low;
        else
            return StreamingPriority.Medium;
    }
}

스트리밍 UI 시스템

c#
using UnityEngine;
using UnityEngine.UI;

public class StreamingProgressUI : MonoBehaviour
{
    [Header("UI 컴포넌트")]
    public Slider progressBar;
    public Text progressText;
    public Text statusText;
    public Button playButton;
    public GameObject loadingPanel;
    
    [Header("설정")]
    public float minimumDisplayTime = 2f; // 최소 로딩 화면 표시 시간
    public bool hideProgressAfterReady = true;
    
    private float displayStartTime;
    private bool isGameReady = false;
    
    void Start()
    {
        displayStartTime = Time.realtimeSinceStartup;
        
        // 이벤트 리스너 등록
        AppsInToss.OnEvent += HandleAppsInTossEvent;
        
        // 초기 UI 설정
        playButton.interactable = false;
        loadingPanel.SetActive(true);
        
        UpdateProgressDisplay();
    }
    
    void Update()
    {
        UpdateProgressDisplay();
        
        // 최소 표시 시간 후 게임 시작 가능
        if (isGameReady && 
            Time.realtimeSinceStartup - displayStartTime >= minimumDisplayTime)
        {
            if (hideProgressAfterReady)
            {
                loadingPanel.SetActive(false);
            }
            
            playButton.interactable = true;
        }
    }
    
    void UpdateProgressDisplay()
    {
        if (AppsInTossAutoStreaming.Instance == null) return;
        
        float progress = AppsInTossAutoStreaming.Instance.GetLoadingProgress();
        bool initialComplete = AppsInTossAutoStreaming.Instance.IsInitialLoadComplete();
        
        // 진행률 업데이트
        progressBar.value = progress;
        progressText.text = $"{progress * 100f:F0}%";
        
        // 상태 텍스트 업데이트
        if (initialComplete)
        {
            statusText.text = "게임 시작 준비 완료!";
            statusText.color = Color.green;
        }
        else
        {
            statusText.text = "에셋 로딩 중...";
            statusText.color = Color.white;
        }
    }
    
    void HandleAppsInTossEvent(string eventName, Dictionary<string, object> data)
    {
        switch (eventName)
        {
            case "game_ready":
                isGameReady = true;
                break;
                
            case "streaming_progress":
                string assetName = data["asset_name"] as string;
                float assetProgress = (float)data["progress"];
                
                // 개별 에셋 진행률 표시
                statusText.text = $"로딩 중: {assetName} ({assetProgress * 100f:F0}%)";
                break;
        }
    }
    
    public void OnPlayButtonClicked()
    {
        if (isGameReady)
        {
            // 게임 씬 로드
            UnityEngine.SceneManagement.SceneManager.LoadScene("GameScene");
        }
    }
    
    void OnDestroy()
    {
        AppsInToss.OnEvent -= HandleAppsInTossEvent;
    }
}

3. 성능 모니터링

스트리밍 성능 추적

c#
public class StreamingPerformanceMonitor : MonoBehaviour
{
    [System.Serializable]
    public class PerformanceMetrics
    {
        public float initialLoadTime;
        public float totalDownloadSize;
        public int failedDownloads;
        public float averageDownloadSpeed;
        public string networkType;
        public Dictionary<string, float> assetLoadTimes;
        
        public PerformanceMetrics()
        {
            assetLoadTimes = new Dictionary<string, float>();
        }
    }
    
    public PerformanceMetrics metrics = new PerformanceMetrics();
    
    void Start()
    {
        AppsInToss.OnAnalytics += HandleAnalyticsEvent;
    }
    
    void HandleAnalyticsEvent(string eventName, Dictionary<string, object> data)
    {
        if (eventName == "streaming_performance")
        {
            string assetName = data["asset_name"] as string;
            bool success = (bool)data["success"];
            float downloadTime = (float)data["download_time"];
            float sizeMB = (float)data["size_mb"];
            
            if (success)
            {
                metrics.assetLoadTimes[assetName] = downloadTime;
                metrics.totalDownloadSize += sizeMB;
                
                if (downloadTime > 0)
                {
                    float speedMBps = sizeMB / downloadTime;
                    UpdateAverageSpeed(speedMBps);
                }
            }
            else
            {
                metrics.failedDownloads++;
            }
            
            // 성능 보고서 생성
            if (metrics.assetLoadTimes.Count % 10 == 0)
            {
                GeneratePerformanceReport();
            }
        }
    }
    
    void UpdateAverageSpeed(float newSpeed)
    {
        if (metrics.averageDownloadSpeed == 0)
        {
            metrics.averageDownloadSpeed = newSpeed;
        }
        else
        {
            metrics.averageDownloadSpeed = (metrics.averageDownloadSpeed + newSpeed) / 2f;
        }
    }
    
    void GeneratePerformanceReport()
    {
        var report = new Dictionary<string, object>
        {
            {"total_assets_loaded", metrics.assetLoadTimes.Count},
            {"total_download_size_mb", metrics.totalDownloadSize},
            {"failed_downloads", metrics.failedDownloads},
            {"average_download_speed_mbps", metrics.averageDownloadSpeed},
            {"network_type", Application.internetReachability.ToString()},
            {"device_model", SystemInfo.deviceModel},
            {"timestamp", System.DateTime.UtcNow.ToString("o")}
        };
        
        AppsInToss.SendAnalytics("streaming_report", report);
        Debug.Log($"스트리밍 성능 보고서 전송: {metrics.assetLoadTimes.Count}개 에셋 로딩 완료");
    }
    
    void OnDestroy()
    {
        AppsInToss.OnAnalytics -= HandleAnalyticsEvent;
    }
}

4. 에디터 도구

AutoStreaming 설정 도구

c#
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;

public class AutoStreamingConfigWindow : EditorWindow
{
    private AppsInTossAutoStreaming streamingComponent;
    private Vector2 scrollPosition;
    private bool showAnalytics = true;
    
    [MenuItem("AppsInToss/AutoStreaming 설정")]
    public static void ShowWindow()
    {
        GetWindow<AutoStreamingConfigWindow>("AutoStreaming 설정");
    }
    
    void OnGUI()
    {
        GUILayout.Label("AutoStreaming 설정", EditorStyles.boldLabel);
        
        // 컴포넌트 참조
        streamingComponent = EditorGUILayout.ObjectField(
            "AutoStreaming 컴포넌트", 
            streamingComponent, 
            typeof(AppsInTossAutoStreaming), 
            true
        ) as AppsInTossAutoStreaming;
        
        if (streamingComponent == null)
        {
            EditorGUILayout.HelpBox("AutoStreaming 컴포넌트를 선택해주세요.", MessageType.Warning);
            return;
        }
        
        EditorGUILayout.Space();
        
        // 에셋 분석 결과 표시
        if (GUILayout.Button("프로젝트 에셋 분석"))
        {
            AnalyzeProjectAssets();
        }
        
        EditorGUILayout.Space();
        
        // 스트리밍 설정 표시
        scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
        
        SerializedObject so = new SerializedObject(streamingComponent);
        SerializedProperty streamingAssets = so.FindProperty("streamingAssets");
        
        EditorGUILayout.PropertyField(streamingAssets, true);
        
        so.ApplyModifiedProperties();
        
        EditorGUILayout.EndScrollView();
        
        EditorGUILayout.Space();
        
        // 유틸리티 버튼들
        EditorGUILayout.BeginHorizontal();
        
        if (GUILayout.Button("설정 검증"))
        {
            ValidateStreamingConfig();
        }
        
        if (GUILayout.Button("최적화 제안"))
        {
            SuggestOptimizations();
        }
        
        if (GUILayout.Button("테스트 빌드"))
        {
            BuildStreamingTest();
        }
        
        EditorGUILayout.EndHorizontal();
    }
    
    void AnalyzeProjectAssets()
    {
        var assetGuids = AssetDatabase.FindAssets("t:Object");
        var assetSizes = new Dictionary<string, float>();
        
        foreach (var guid in assetGuids)
        {
            string assetPath = AssetDatabase.GUIDToAssetPath(guid);
            var fileInfo = new FileInfo(assetPath);
            
            if (fileInfo.Exists)
            {
                float sizeMB = fileInfo.Length / (1024f * 1024f);
                assetSizes[Path.GetFileName(assetPath)] = sizeMB;
            }
        }
        
        // 크기별 정렬
        var sortedAssets = new List<KeyValuePair<string, float>>(assetSizes);
        sortedAssets.Sort((a, b) => b.Value.CompareTo(a.Value));
        
        Debug.Log("=== 프로젝트 에셋 크기 분석 ===");
        for (int i = 0; i < Mathf.Min(10, sortedAssets.Count); i++)
        {
            var asset = sortedAssets[i];
            Debug.Log($"{i + 1}. {asset.Key}: {asset.Value:F2}MB");
        }
        
        float totalSizeMB = 0f;
        foreach (var size in assetSizes.Values)
        {
            totalSizeMB += size;
        }
        
        Debug.Log($"총 에셋 크기: {totalSizeMB:F2}MB");
        Debug.Log($"권장 즉시 로딩 크기: {streamingComponent.initialLoadThresholdMB}MB");
    }
    
    void ValidateStreamingConfig()
    {
        var issues = new List<string>();
        
        // 설정 검증
        if (streamingComponent.streamingAssets.Length == 0)
        {
            issues.Add("스트리밍 에셋이 설정되지 않았습니다.");
        }
        
        // 즉시 로딩 에셋 크기 체크
        float immediateSizeMB = 0f;
        foreach (var asset in streamingComponent.streamingAssets)
        {
            if (asset.priority == AppsInTossAutoStreaming.StreamingPriority.Immediate)
            {
                immediateSizeMB += asset.sizeMB;
            }
        }
        
        if (immediateSizeMB > streamingComponent.initialLoadThresholdMB)
        {
            issues.Add($"즉시 로딩 에셋 크기가 임계값을 초과합니다: {immediateSizeMB:F1}MB > {streamingComponent.initialLoadThresholdMB}MB");
        }
        
        // 결과 표시
        if (issues.Count == 0)
        {
            EditorUtility.DisplayDialog("검증 완료", "AutoStreaming 설정이 올바릅니다.", "확인");
        }
        else
        {
            string message = "다음 문제들을 해결해주세요:\n\n" + string.Join("\n", issues);
            EditorUtility.DisplayDialog("검증 실패", message, "확인");
        }
    }
    
    void SuggestOptimizations()
    {
        var suggestions = new List<string>();
        
        // 큰 에셋 우선순위 확인
        foreach (var asset in streamingComponent.streamingAssets)
        {
            if (asset.sizeMB > 5f && asset.priority == AppsInTossAutoStreaming.StreamingPriority.Immediate)
            {
                suggestions.Add($"큰 에셋 '{asset.assetName}' ({asset.sizeMB:F1}MB)의 우선순위를 낮추는 것을 고려해보세요.");
            }
        }
        
        // 의존성 체크
        var dependencyCount = new Dictionary<string, int>();
        foreach (var asset in streamingComponent.streamingAssets)
        {
            foreach (var dep in asset.dependencies)
            {
                dependencyCount[dep] = dependencyCount.ContainsKey(dep) ? dependencyCount[dep] + 1 : 1;
            }
        }
        
        foreach (var kvp in dependencyCount)
        {
            if (kvp.Value > 3)
            {
                suggestions.Add($"에셋 '{kvp.Key}'이 {kvp.Value}개의 다른 에셋에서 참조됩니다. 공통 번들로 분리를 고려해보세요.");
            }
        }
        
        // 결과 표시
        if (suggestions.Count == 0)
        {
            EditorUtility.DisplayDialog("최적화 분석", "현재 설정이 최적화되어 있습니다.", "확인");
        }
        else
        {
            string message = "다음 최적화를 고려해보세요:\n\n" + string.Join("\n\n", suggestions);
            EditorUtility.DisplayDialog("최적화 제안", message, "확인");
        }
    }
    
    void BuildStreamingTest()
    {
        if (EditorUtility.DisplayDialog("테스트 빌드", "AutoStreaming 테스트 빌드를 시작하시겠습니까?", "시작", "취소"))
        {
            // 테스트용 빌드 설정
            BuildPlayerOptions buildOptions = new BuildPlayerOptions();
            buildOptions.scenes = new[] { EditorBuildSettings.scenes[0].path };
            buildOptions.locationPathName = "Builds/StreamingTest/StreamingTest";
            buildOptions.target = BuildTarget.WebGL;
            buildOptions.options = BuildOptions.Development;
            
            BuildPipeline.BuildPlayer(buildOptions);
            
            Debug.Log("AutoStreaming 테스트 빌드 완료");
        }
    }
}
#endif

AutoStreaming을 통해 사용자가 즉시 게임을 시작할 수 있도록 하되, 백그라운드에서 추가 콘텐츠를 스마트하게 로딩하여 끊김 없는 게임 경험을 제공하세요.