첫 씬 시작 최적화 가이드
앱인토스 Unity 게임에서 첫 번째 씬의 로딩 시간을 최소화하여 즉시 플레이 가능한 경험을 제공하는 방법을 다뤄요.
1. 첫 씬 최적화 전략
첫 씬 구조 최적화
🚀 최적화된 첫 씬 구조
├── 핵심 게임 요소 (즉시 로딩)
│ ├── 플레이어 컨트롤러
│ ├── 기본 UI 시스템
│ ├── 입력 관리자
│ └── 게임 매니저
├── 시각적 요소 (단계적 로딩)
│ ├── 배경 이미지 (저해상도 → 고해상도)
│ ├── 캐릭터 모델 (LOD 0 → LOD 상위)
│ ├── 환경 오브젝트 (필수 → 장식)
│ └── 파티클 효과 (기본 → 고급)
├── 오디오 (지연 로딩)
│ ├── 배경 음악
│ ├── 효과음
│ └── 음성
└── 비핵심 시스템 (백그라운드 로딩)
├── 분석 시스템
├── 소셜 기능
├── 광고 시스템
└── 업데이트 체크첫 씬 최적화 매니저
c#
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
public class FirstSceneOptimizer : MonoBehaviour
{
public static FirstSceneOptimizer Instance { get; private set; }
[System.Serializable]
public class OptimizationSettings
{
[Header("로딩 우선순위")]
public GameObject[] criticalObjects;
public GameObject[] secondaryObjects;
public GameObject[] optionalObjects;
[Header("품질 설정")]
public int initialQualityLevel = 0;
public bool enableProgressiveLOD = true;
public bool enableAsyncLoading = true;
[Header("시간 제한")]
public float maxInitialLoadTime = 2f;
public float maxSecondaryLoadTime = 5f;
}
[Header("최적화 설정")]
public OptimizationSettings settings;
// 내부 상태
private bool isOptimizationComplete = false;
private float optimizationStartTime;
private List<GameObject> loadedObjects = new List<GameObject>();
void Awake()
{
if (Instance == null)
{
Instance = this;
StartFirstSceneOptimization();
}
else
{
Destroy(gameObject);
}
}
void StartFirstSceneOptimization()
{
optimizationStartTime = Time.realtimeSinceStartup;
Debug.Log("첫 씬 최적화 시작");
// 초기 품질 설정
QualitySettings.SetQualityLevel(settings.initialQualityLevel);
// 단계별 로딩 시작
StartCoroutine(OptimizedLoadingSequence());
}
IEnumerator OptimizedLoadingSequence()
{
// 1단계: 즉시 필요한 핵심 오브젝트 활성화
yield return StartCoroutine(LoadCriticalObjects());
// 첫 프레임 렌더링을 위해 대기
yield return null;
// 2단계: 2차 오브젝트 로딩
StartCoroutine(LoadSecondaryObjects());
// 3단계: 선택적 오브젝트 백그라운드 로딩
StartCoroutine(LoadOptionalObjects());
// 최적화 완료
CompleteOptimization();
}
IEnumerator LoadCriticalObjects()
{
Debug.Log("핵심 오브젝트 로딩 시작");
foreach (var obj in settings.criticalObjects)
{
if (obj != null)
{
obj.SetActive(true);
loadedObjects.Add(obj);
// 프레임 분산을 위해 대기
yield return null;
}
}
float loadTime = Time.realtimeSinceStartup - optimizationStartTime;
Debug.Log($"핵심 오브젝트 로딩 완료: {loadTime:F2}초");
// 게임 시작 가능 신호
SendGameReadySignal();
}
IEnumerator LoadSecondaryObjects()
{
float startTime = Time.realtimeSinceStartup;
foreach (var obj in settings.secondaryObjects)
{
// 시간 제한 체크
if (Time.realtimeSinceStartup - startTime > settings.maxSecondaryLoadTime)
{
break;
}
if (obj != null)
{
obj.SetActive(true);
loadedObjects.Add(obj);
// 프레임 분산
yield return null;
}
}
Debug.Log("2차 오브젝트 로딩 완료");
}
IEnumerator LoadOptionalObjects()
{
// 플레이어가 활발하지 않을 때만 로딩
while (true)
{
if (IsPlayerIdle())
{
foreach (var obj in settings.optionalObjects)
{
if (obj != null && !obj.activeInHierarchy)
{
obj.SetActive(true);
loadedObjects.Add(obj);
// 여러 프레임에 걸쳐 분산
for (int i = 0; i < 3; i++)
{
yield return null;
}
// 플레이어가 다시 활성화되면 중단
if (!IsPlayerIdle())
{
break;
}
}
}
}
yield return new WaitForSeconds(1f);
}
}
bool IsPlayerIdle()
{
// 간단한 유휴 상태 감지 로직
return Time.realtimeSinceStartup - Time.time > 3f;
}
void SendGameReadySignal()
{
float readyTime = Time.realtimeSinceStartup - optimizationStartTime;
var readyData = new Dictionary<string, object>
{
{"ready_time", readyTime},
{"loaded_objects", loadedObjects.Count},
{"quality_level", QualitySettings.GetQualityLevel()}
};
AppsInToss.SendEvent("first_scene_ready", readyData);
Debug.Log($"첫 씬 준비 완료: {readyTime:F2}초");
}
void CompleteOptimization()
{
isOptimizationComplete = true;
// 점진적 품질 향상 시작
if (settings.enableProgressiveLOD)
{
StartCoroutine(ProgressiveQualityImprovement());
}
float totalTime = Time.realtimeSinceStartup - optimizationStartTime;
// 최적화 완료 분석 데이터
var analyticsData = new Dictionary<string, object>
{
{"optimization_time", totalTime},
{"objects_loaded", loadedObjects.Count},
{"device_model", SystemInfo.deviceModel},
{"memory_usage", System.GC.GetTotalMemory(false) / (1024f * 1024f)}
};
AppsInToss.SendAnalytics("first_scene_optimization", analyticsData);
}
IEnumerator ProgressiveQualityImprovement()
{
// 몇 초 대기 후 품질 점진적 향상
yield return new WaitForSeconds(3f);
int maxQuality = QualitySettings.names.Length - 1;
int currentQuality = QualitySettings.GetQualityLevel();
while (currentQuality < maxQuality)
{
// 성능 여유가 있을 때만 품질 향상
if (Application.targetFrameRate <= 0 || Time.smoothDeltaTime * Application.targetFrameRate < 1.2f)
{
currentQuality++;
QualitySettings.SetQualityLevel(currentQuality);
Debug.Log($"품질 레벨 향상: {currentQuality}");
// 품질 변경 후 안정화 대기
yield return new WaitForSeconds(2f);
}
else
{
// 성능이 부족하면 대기
yield return new WaitForSeconds(5f);
}
}
}
// 공개 API
public bool IsOptimizationComplete()
{
return isOptimizationComplete;
}
public float GetOptimizationProgress()
{
if (isOptimizationComplete) return 1f;
int totalObjects = settings.criticalObjects.Length +
settings.secondaryObjects.Length +
settings.optionalObjects.Length;
return totalObjects > 0 ? (float)loadedObjects.Count / totalObjects : 0f;
}
public void ForceCompleteOptimization()
{
StopAllCoroutines();
// 모든 오브젝트 즉시 활성화
foreach (var obj in settings.criticalObjects)
{
if (obj != null) obj.SetActive(true);
}
foreach (var obj in settings.secondaryObjects)
{
if (obj != null) obj.SetActive(true);
}
CompleteOptimization();
}
}2. 성능 모니터링
실시간 성능 추적
c#
public class FirstScenePerformanceMonitor : MonoBehaviour
{
[Header("성능 임계값")]
public float targetFPS = 30f;
public float maxLoadTime = 3f;
public float maxMemoryUsageMB = 200f;
private float sceneStartTime;
private List<float> frameTimes = new List<float>();
void Start()
{
sceneStartTime = Time.realtimeSinceStartup;
StartCoroutine(MonitorPerformance());
}
IEnumerator MonitorPerformance()
{
while (true)
{
// FPS 모니터링
float frameTime = Time.smoothDeltaTime;
frameTimes.Add(frameTime);
if (frameTimes.Count > 60) // 2초간의 프레임 타임 유지
{
frameTimes.RemoveAt(0);
}
// 성능 문제 감지
CheckPerformanceIssues();
yield return null;
}
}
void CheckPerformanceIssues()
{
// FPS 체크
if (frameTimes.Count > 30)
{
float avgFrameTime = 0f;
for (int i = frameTimes.Count - 30; i < frameTimes.Count; i++)
{
avgFrameTime += frameTimes[i];
}
avgFrameTime /= 30f;
float currentFPS = 1f / avgFrameTime;
if (currentFPS < targetFPS * 0.8f) // 20% 여유
{
OnPerformanceIssueDetected("Low FPS", currentFPS);
}
}
// 메모리 사용량 체크
float memoryUsageMB = System.GC.GetTotalMemory(false) / (1024f * 1024f);
if (memoryUsageMB > maxMemoryUsageMB)
{
OnPerformanceIssueDetected("High Memory Usage", memoryUsageMB);
}
// 로딩 시간 체크
float loadTime = Time.realtimeSinceStartup - sceneStartTime;
if (loadTime > maxLoadTime && !FirstSceneOptimizer.Instance.IsOptimizationComplete())
{
OnPerformanceIssueDetected("Slow Loading", loadTime);
}
}
void OnPerformanceIssueDetected(string issueType, float value)
{
Debug.LogWarning($"성능 문제 감지: {issueType} = {value:F2}");
// 자동 최적화 시도
TryAutoOptimization(issueType);
// 분석 데이터 전송
var issueData = new Dictionary<string, object>
{
{"issue_type", issueType},
{"value", value},
{"scene_time", Time.realtimeSinceStartup - sceneStartTime},
{"device_model", SystemInfo.deviceModel}
};
AppsInToss.SendAnalytics("first_scene_performance_issue", issueData);
}
void TryAutoOptimization(string issueType)
{
switch (issueType)
{
case "Low FPS":
// 품질 레벨 낮추기
int currentQuality = QualitySettings.GetQualityLevel();
if (currentQuality > 0)
{
QualitySettings.SetQualityLevel(currentQuality - 1);
Debug.Log($"품질 레벨 자동 조정: {currentQuality - 1}");
}
break;
case "High Memory Usage":
// 강제 가비지 컬렉션
System.GC.Collect();
break;
case "Slow Loading":
// 강제 최적화 완료
if (FirstSceneOptimizer.Instance != null)
{
FirstSceneOptimizer.Instance.ForceCompleteOptimization();
}
break;
}
}
}첫 씬은 사용자의 첫인상을 결정짓는 중요한 순간이에요.
핵심 기능을 우선 로딩하고 나머지는 점진적으로 로딩하여 즉시 플레이 가능한 경험을 제공하세요.
