시작 시간 최적화
앱인토스 Unity 게임의 시작 시간을 최적화하여 사용자 경험을 크게 향상시키는 방법을 제공해요.
토스 앱 사용자들은 빠른 응답성을 기대하므로 5초 이내 시작을 목표로 해요.
1. 시작 시간 개요
목표 시간 기준
🎯 앱인토스 시작 시간 목표
├── Critical (즉시 개선 필요): > 5초
├── Good (권장): 2-3초
└── Excellent (최적): < 2초시작 과정 분석
tsx
// 앱인토스 Unity 시작 과정
interface StartupPhases {
tossAppLaunch: number; // 토스 앱 내 게임 시작: ~0.5초
appsInTossInit: number; // 앱인토스 프레임워크 초기화: ~0.3초
unityEngineInit: number; // Unity 엔진 초기화: ~1.0초
gameAssetLoading: number; // 게임 에셋 로딩: ~1-3초
firstSceneReady: number; // 첫 씬 준비 완료: ~0.5초
}
// 각 단계별 최적화 포인트 식별2. Unity 엔진 최적화
PlayerSettings 최적화
c#
// Unity 프로젝트 설정 최적화
public class AppsInTossStartupOptimizer : MonoBehaviour
{
[Header("시작 최적화 설정")]
public bool enableFastStartup = true;
public bool preloadCriticalAssets = true;
public int maxConcurrentLoads = 3;
[Header("앱인토스 연동")]
public bool enableTossLogin = true;
public bool preloadTossServices = false;
void Awake()
{
if (enableFastStartup)
{
OptimizeStartupSettings();
}
}
void OptimizeStartupSettings()
{
// 프레임률 제한 설정 (초기 로딩 시)
Application.targetFrameRate = 30; // 배터리 절약
// Unity 서비스 지연 초기화
StartCoroutine(DelayedUnityServicesInit());
// 메모리 관리 최적화
System.GC.Collect();
Resources.UnloadUnusedAssets();
if (enableTossLogin)
{
InitializeTossServices();
}
}
IEnumerator DelayedUnityServicesInit()
{
// 첫 프레임 렌더링 후 Unity 서비스 초기화
yield return new WaitForEndOfFrame();
// Analytics, Cloud Build 등 지연 초기화
InitializeUnityServices();
}
void InitializeTossServices()
{
// 토스 로그인 서비스 사전 초기화
StartCoroutine(PreloadTossAuthentication());
}
IEnumerator PreloadTossAuthentication()
{
// 토스페이 SDK 사전 로딩
yield return new WaitForSeconds(0.1f);
// AppsInToss 인증 토큰 검증
AppsInToss.GetCurrentUserToken((token) => {
if (!string.IsNullOrEmpty(token))
{
Debug.Log("토스 인증 토큰 사전 로딩 완료");
// 사용자 프로필 캐시 로딩
StartCoroutine(CacheUserProfile(token));
}
});
}
IEnumerator CacheUserProfile(string token)
{
// 사용자 프로필 정보 미리 캐시
// 게임 플레이 중 빠른 접근을 위함
yield return null; // 구현 필요
}
}스크립트 실행 순서 최적화
c#
// 스크립트 실행 순서 정의
[DefaultExecutionOrder(-100)] // 가장 먼저 실행
public class StartupManager : MonoBehaviour
{
public static StartupManager Instance { get; private set; }
[Header("시작 단계 설정")]
public StartupPhase[] phases;
[System.Serializable]
public class StartupPhase
{
public string name;
public UnityEngine.Events.UnityEvent onPhaseStart;
public UnityEngine.Events.UnityEvent onPhaseComplete;
public float maxDuration = 5f; // 타임아웃
}
private int currentPhaseIndex = 0;
private float phaseStartTime;
void Awake()
{
// 싱글톤 패턴
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
StartStartupSequence();
}
else
{
Destroy(gameObject);
}
}
void StartStartupSequence()
{
Debug.Log("앱인토스 게임 시작 시퀀스 시작");
ExecuteNextPhase();
}
void ExecuteNextPhase()
{
if (currentPhaseIndex >= phases.Length)
{
OnStartupComplete();
return;
}
var phase = phases[currentPhaseIndex];
Debug.Log($"시작 단계 실행: {phase.name}");
phaseStartTime = Time.realtimeSinceStartup;
phase.onPhaseStart?.Invoke();
// 타임아웃 설정
StartCoroutine(PhaseTimeout(phase));
}
IEnumerator PhaseTimeout(StartupPhase phase)
{
yield return new WaitForSeconds(phase.maxDuration);
// 단계가 완료되지 않았다면 강제 진행
if (currentPhaseIndex < phases.Length)
{
Debug.LogWarning($"시작 단계 타임아웃: {phase.name}");
CompleteCurrentPhase();
}
}
public void CompleteCurrentPhase()
{
if (currentPhaseIndex >= phases.Length) return;
var phase = phases[currentPhaseIndex];
float duration = Time.realtimeSinceStartup - phaseStartTime;
Debug.Log($"시작 단계 완료: {phase.name} ({duration:F2}초)");
phase.onPhaseComplete?.Invoke();
// 앱인토스 분석 시스템에 전송
SendPhaseMetric(phase.name, duration);
currentPhaseIndex++;
ExecuteNextPhase();
}
void OnStartupComplete()
{
float totalTime = Time.realtimeSinceStartup;
Debug.Log($"게임 시작 완료! 총 시간: {totalTime:F2}초");
// 앱인토스에 시작 완료 알림
AppsInToss.ReportGameReady(totalTime);
// 성능 목표 체크
if (totalTime > 5f)
{
Debug.LogWarning($"시작 시간이 목표를 초과했습니다: {totalTime:F2}s > 5s");
AppsInToss.ReportPerformanceIssue("startup_slow", totalTime);
}
}
void SendPhaseMetric(string phaseName, float duration)
{
var metric = new Dictionary<string, object>
{
{"phase", phaseName},
{"duration", duration},
{"timestamp", System.DateTime.UtcNow.ToString("o")}
};
AppsInToss.SendAnalytics("startup_phase", metric);
}
}3. 에셋 로딩 최적화
핵심 에셋 사전 로딩
c#
public class CriticalAssetLoader : MonoBehaviour
{
[Header("필수 에셋 목록")]
public UnityEngine.Object[] criticalAssets;
public string[] criticalAddressables;
[Header("앱인토스 특화 에셋")]
public Texture2D tossPayIcon;
public AudioClip tossNotificationSound;
public Font tossFont; // 앱인토스 권장 폰트
private int loadedAssetCount = 0;
private int totalAssetCount = 0;
public System.Action<float> OnLoadProgress;
public System.Action OnLoadComplete;
public IEnumerator LoadCriticalAssets()
{
totalAssetCount = criticalAssets.Length + criticalAddressables.Length + 3; // 앱인토스 에셋
loadedAssetCount = 0;
// 1. Unity Resources 에셋 로딩
foreach (var asset in criticalAssets)
{
if (asset != null)
{
// 이미 로딩된 에셋은 스킵
yield return null; // 프레임 분산
loadedAssetCount++;
ReportProgress();
}
}
// 2. Addressables 에셋 로딩
foreach (var address in criticalAddressables)
{
yield return StartCoroutine(LoadAddressableAsset(address));
}
// 3. 앱인토스 특화 에셋 로딩
yield return StartCoroutine(LoadAppsInTossAssets());
OnLoadComplete?.Invoke();
}
IEnumerator LoadAddressableAsset(string address)
{
var handle = Addressables.LoadAssetAsync<UnityEngine.Object>(address);
while (!handle.IsDone)
{
yield return null;
}
if (handle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
Debug.Log($"Addressable 에셋 로딩 완료: {address}");
}
else
{
Debug.LogError($"Addressable 에셋 로딩 실패: {address}");
}
loadedAssetCount++;
ReportProgress();
}
IEnumerator LoadAppsInTossAssets()
{
// 토스페이 아이콘 로딩
if (tossPayIcon == null)
{
var iconLoad = Resources.LoadAsync<Texture2D>("AppsInToss/TossPayIcon");
yield return iconLoad;
tossPayIcon = iconLoad.asset as Texture2D;
}
loadedAssetCount++;
ReportProgress();
// 토스 알림음 로딩
if (tossNotificationSound == null)
{
var soundLoad = Resources.LoadAsync<AudioClip>("AppsInToss/NotificationSound");
yield return soundLoad;
tossNotificationSound = soundLoad.asset as AudioClip;
}
loadedAssetCount++;
ReportProgress();
// 앱인토스 권장 폰트 로딩
if (tossFont == null)
{
var fontLoad = Resources.LoadAsync<Font>("AppsInToss/NotoSansKR");
yield return fontLoad;
tossFont = fontLoad.asset as Font;
}
loadedAssetCount++;
ReportProgress();
}
void ReportProgress()
{
float progress = (float)loadedAssetCount / totalAssetCount;
OnLoadProgress?.Invoke(progress);
// 앱인토스 로딩 진행률 UI 업데이트
AppsInToss.UpdateLoadingProgress(progress);
}
}점진적 로딩 시스템
c#
public class ProgressiveLoader : MonoBehaviour
{
[System.Serializable]
public class LoadingTier
{
public string name;
public UnityEngine.Object[] assets;
public int priority; // 낮을수록 우선순위 높음
public bool loadInBackground = true;
}
[Header("로딩 단계 설정")]
public LoadingTier[] loadingTiers;
[Header("앱인토스 연동 설정")]
public bool showTossLoadingScreen = true;
public bool enableBackgroundLoading = true;
void Start()
{
StartCoroutine(ProgressiveLoadingSequence());
}
IEnumerator ProgressiveLoadingSequence()
{
// 우선순위별로 정렬
var sortedTiers = loadingTiers.OrderBy(t => t.priority).ToArray();
foreach (var tier in sortedTiers)
{
if (tier.priority == 0) // 최우선 에셋은 동기 로딩
{
yield return StartCoroutine(LoadTierSync(tier));
}
else if (enableBackgroundLoading) // 나머지는 백그라운드 로딩
{
StartCoroutine(LoadTierAsync(tier));
}
}
}
IEnumerator LoadTierSync(LoadingTier tier)
{
Debug.Log($"동기 로딩 시작: {tier.name}");
foreach (var asset in tier.assets)
{
if (asset != null)
{
// 에셋 사전 로딩
yield return null; // 프레임 분산
}
}
Debug.Log($"동기 로딩 완료: {tier.name}");
}
IEnumerator LoadTierAsync(LoadingTier tier)
{
Debug.Log($"백그라운드 로딩 시작: {tier.name}");
// 백그라운드 로딩 (게임 플레이에 영향 없음)
foreach (var asset in tier.assets)
{
if (asset != null)
{
yield return new WaitForSeconds(0.1f); // 부하 분산
}
}
Debug.Log($"백그라운드 로딩 완료: {tier.name}");
}
}4. 네트워크 최적화
앱인토스 API 사전 초기화
tsx
// TypeScript - 앱인토스 서비스 사전 초기화
interface AppsInTossPreloadConfig {
enableUserAuth: boolean;
enableTossPay: boolean;
enableAnalytics: boolean;
cacheUserData: boolean;
}
class AppsInTossPreloader {
private config: AppsInTossPreloadConfig;
private preloadPromises: Promise<any>[] = [];
constructor(config: AppsInTossPreloadConfig) {
this.config = config;
}
async preloadServices(): Promise<void> {
console.log('앱인토스 서비스 사전 로딩 시작');
if (this.config.enableUserAuth) {
this.preloadPromises.push(this.preloadAuthentication());
}
if (this.config.enableTossPay) {
this.preloadPromises.push(this.preloadTossPayServices());
}
if (this.config.enableAnalytics) {
this.preloadPromises.push(this.preloadAnalyticsServices());
}
if (this.config.cacheUserData) {
this.preloadPromises.push(this.preloadUserData());
}
// 병렬로 모든 서비스 사전 로딩
await Promise.all(this.preloadPromises);
console.log('앱인토스 서비스 사전 로딩 완료');
}
private async preloadAuthentication(): Promise<void> {
try {
// 토스 인증 토큰 검증
const token = await AppsInToss.getCurrentUserToken();
if (token) {
console.log('사용자 인증 정보 캐시 완료');
localStorage.setItem('ait_user_token_cache', token);
}
} catch (error) {
console.warn('인증 정보 사전 로딩 실패:', error);
}
}
private async preloadTossPayServices(): Promise<void> {
try {
// 토스페이 결제 수단 목록 캐시
const paymentMethods = await AppsInToss.getAvailablePaymentMethods();
localStorage.setItem('ait_payment_methods_cache', JSON.stringify(paymentMethods));
console.log('토스페이 서비스 캐시 완료');
} catch (error) {
console.warn('토스페이 서비스 사전 로딩 실패:', error);
}
}
private async preloadAnalyticsServices(): Promise<void> {
try {
// 분석 서비스 초기화
await AppsInToss.initializeAnalytics();
console.log('분석 서비스 초기화 완료');
} catch (error) {
console.warn('분석 서비스 초기화 실패:', error);
}
}
private async preloadUserData(): Promise<void> {
try {
// 사용자 프로필 및 게임 데이터 캐시
const userProfile = await AppsInToss.getUserProfile();
const gameData = await AppsInToss.getCloudSaveData();
localStorage.setItem('ait_user_profile_cache', JSON.stringify(userProfile));
localStorage.setItem('ait_game_data_cache', JSON.stringify(gameData));
console.log('사용자 데이터 캐시 완료');
} catch (error) {
console.warn('사용자 데이터 캐시 실패:', error);
}
}
}
// Unity WebGL에서 호출할 전역 함수
(window as any).initializeAppsInTossServices = async (configJson: string) => {
const config = JSON.parse(configJson) as AppsInTossPreloadConfig;
const preloader = new AppsInTossPreloader(config);
try {
await preloader.preloadServices();
// Unity에 완료 알림
(window as any).unityInstance.SendMessage('StartupManager', 'OnAppsInTossServicesReady', '');
} catch (error) {
console.error('앱인토스 서비스 초기화 실패:', error);
(window as any).unityInstance.SendMessage('StartupManager', 'OnAppsInTossServicesError', error.message);
}
};5. 메모리 관리 최적화
시작 시 메모리 정리
c#
public class StartupMemoryManager : MonoBehaviour
{
[Header("메모리 최적화 설정")]
public bool enableStartupGC = true;
public bool unloadUnusedAssets = true;
public bool optimizeTextureMemory = true;
[Header("앱인토스 메모리 제한")]
public long maxMemoryUsageMB = 200; // 앱인토스 권장 제한
void Awake()
{
if (enableStartupGC)
{
StartCoroutine(OptimizeStartupMemory());
}
}
IEnumerator OptimizeStartupMemory()
{
Debug.Log("시작 시 메모리 최적화 시작");
// 1. 가비지 컬렉션 실행
if (enableStartupGC)
{
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
yield return new WaitForSeconds(0.1f);
}
// 2. 사용하지 않는 에셋 언로드
if (unloadUnusedAssets)
{
var unloadOp = Resources.UnloadUnusedAssets();
yield return unloadOp;
}
// 3. 텍스처 메모리 최적화
if (optimizeTextureMemory)
{
OptimizeTextureSettings();
}
// 4. 메모리 사용량 체크
CheckMemoryUsage();
Debug.Log("시작 시 메모리 최적화 완료");
}
void OptimizeTextureSettings()
{
// 모바일 환경에 맞는 텍스처 설정 적용
QualitySettings.masterTextureLimit = 1; // 절반 해상도
QualitySettings.anisotropicFiltering = AnisotropicFiltering.Disable;
Debug.Log("텍스처 메모리 최적화 적용");
}
void CheckMemoryUsage()
{
long currentMemoryMB = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemory(false) / (1024 * 1024);
Debug.Log($"현재 메모리 사용량: {currentMemoryMB}MB");
if (currentMemoryMB > maxMemoryUsageMB)
{
Debug.LogWarning($"메모리 사용량이 권장 제한을 초과: {currentMemoryMB}MB > {maxMemoryUsageMB}MB");
// 앱인토스에 메모리 경고 리포트
AppsInToss.ReportPerformanceIssue("high_memory_startup", currentMemoryMB);
// 추가 메모리 정리 시도
StartCoroutine(AdditionalMemoryCleanup());
}
}
IEnumerator AdditionalMemoryCleanup()
{
// 더 적극적인 메모리 정리
Resources.UnloadUnusedAssets();
System.GC.Collect();
yield return new WaitForSeconds(0.5f);
// 정리 후 재측정
long newMemoryMB = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemory(false) / (1024 * 1024);
Debug.Log($"메모리 정리 후 사용량: {newMemoryMB}MB");
}
}6. 시작 화면 최적화
앱인토스 로딩 화면 커스터마이징
c#
public class AppsInTossLoadingScreen : MonoBehaviour
{
[Header("로딩 화면 설정")]
public CanvasGroup loadingCanvas;
public Image progressBar;
public TextMeshProUGUI loadingText;
public Image tossLogo;
[Header("앱인토스 브랜딩")]
public Color tossBlue = new Color(0.196f, 0.510f, 0.965f); // #3182F6
public Sprite tossIcon;
public string[] loadingMessages = {
"토스와 함께하는 게임을 준비하고 있어요",
"곧 시작됩니다!",
"로딩 중..."
};
private int currentMessageIndex = 0;
void Start()
{
InitializeLoadingScreen();
StartCoroutine(AnimateLoadingScreen());
}
void InitializeLoadingScreen()
{
// 앱인토스 브랜드 컬러 적용
progressBar.color = tossBlue;
if (tossIcon != null)
{
tossLogo.sprite = tossIcon;
}
// 초기 메시지 설정
if (loadingText != null)
{
loadingText.text = loadingMessages[0];
loadingText.color = tossBlue;
}
}
IEnumerator AnimateLoadingScreen()
{
// 로딩 메시지 순환 표시
while (loadingCanvas.alpha > 0)
{
yield return new WaitForSeconds(2f);
currentMessageIndex = (currentMessageIndex + 1) % loadingMessages.Length;
loadingText.text = loadingMessages[currentMessageIndex];
// 부드러운 텍스트 전환 효과
yield return StartCoroutine(FadeText());
}
}
IEnumerator FadeText()
{
// 페이드 아웃
float alpha = 1f;
while (alpha > 0)
{
alpha -= Time.deltaTime * 2f;
loadingText.alpha = alpha;
yield return null;
}
// 페이드 인
while (alpha < 1)
{
alpha += Time.deltaTime * 2f;
loadingText.alpha = alpha;
yield return null;
}
}
public void UpdateProgress(float progress)
{
if (progressBar != null)
{
progressBar.fillAmount = Mathf.Clamp01(progress);
}
// 진행률에 따른 메시지 업데이트
if (progress > 0.8f && currentMessageIndex < loadingMessages.Length - 1)
{
loadingText.text = loadingMessages[loadingMessages.Length - 1];
}
}
public void HideLoadingScreen()
{
StartCoroutine(FadeOutLoadingScreen());
}
IEnumerator FadeOutLoadingScreen()
{
float alpha = 1f;
while (alpha > 0)
{
alpha -= Time.deltaTime * 3f; // 빠른 페이드아웃
loadingCanvas.alpha = alpha;
yield return null;
}
loadingCanvas.gameObject.SetActive(false);
// 앱인토스에 로딩 완료 알림
AppsInToss.OnLoadingComplete();
}
}7. 성능 모니터링
시작 성능 추적 시스템
c#
public class StartupPerformanceTracker : MonoBehaviour
{
[System.Serializable]
public class StartupMetrics
{
public float totalStartupTime;
public float engineInitTime;
public float assetLoadingTime;
public float appsInTossInitTime;
public float firstFrameTime;
public long peakMemoryUsage;
public string deviceModel;
public string osVersion;
}
private StartupMetrics metrics = new StartupMetrics();
private float startTime;
void Awake()
{
startTime = Time.realtimeSinceStartup;
StartPerformanceTracking();
}
void StartPerformanceTracking()
{
// 기기 정보 수집
metrics.deviceModel = SystemInfo.deviceModel;
metrics.osVersion = SystemInfo.operatingSystem;
StartCoroutine(TrackStartupPerformance());
}
IEnumerator TrackStartupPerformance()
{
// 첫 프레임 시간 측정
yield return new WaitForEndOfFrame();
metrics.firstFrameTime = Time.realtimeSinceStartup - startTime;
// 메모리 사용량 추적
StartCoroutine(TrackMemoryUsage());
// 시작 완료 대기
yield return new WaitUntil(() => StartupManager.Instance != null && StartupManager.Instance.IsStartupComplete);
// 최종 메트릭 수집
CollectFinalMetrics();
// 앱인토스 분석 시스템에 전송
SendStartupMetrics();
}
IEnumerator TrackMemoryUsage()
{
while (!IsStartupComplete())
{
long currentMemory = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemory(false);
metrics.peakMemoryUsage = System.Math.Max(metrics.peakMemoryUsage, currentMemory);
yield return new WaitForSeconds(0.1f);
}
}
void CollectFinalMetrics()
{
metrics.totalStartupTime = Time.realtimeSinceStartup - startTime;
Debug.Log($"시작 성능 메트릭:");
Debug.Log($" 총 시작 시간: {metrics.totalStartupTime:F2}초");
Debug.Log($" 첫 프레임: {metrics.firstFrameTime:F2}초");
Debug.Log($" 최대 메모리: {metrics.peakMemoryUsage / (1024*1024)}MB");
Debug.Log($" 기기: {metrics.deviceModel}");
}
void SendStartupMetrics()
{
var metricsData = new Dictionary<string, object>
{
{"total_startup_time", metrics.totalStartupTime},
{"first_frame_time", metrics.firstFrameTime},
{"peak_memory_mb", metrics.peakMemoryUsage / (1024f * 1024f)},
{"device_model", metrics.deviceModel},
{"os_version", metrics.osVersion},
{"timestamp", System.DateTime.UtcNow.ToString("o")}
};
AppsInToss.SendAnalytics("startup_performance", metricsData);
// 성능 기준 체크
if (metrics.totalStartupTime > 5f)
{
AppsInToss.ReportPerformanceIssue("startup_slow", metrics.totalStartupTime);
}
if (metrics.peakMemoryUsage > 200 * 1024 * 1024) // 200MB
{
AppsInToss.ReportPerformanceIssue("high_startup_memory", metrics.peakMemoryUsage / (1024f * 1024f));
}
}
bool IsStartupComplete()
{
return StartupManager.Instance != null && StartupManager.Instance.IsStartupComplete;
}
}8. 체크리스트 및 권장사항
시작 최적화 체크리스트
- Unity PlayerSettings 최적화 적용
- 스크립트 실행 순서 정의
- 핵심 에셋 사전 로딩 시스템 구현
- 점진적 로딩 시스템 적용
- 앱인토스 API 사전 초기화
- 시작 시 메모리 최적화
- 커스텀 로딩 화면 구현
- 성능 모니터링 시스템 설치
- 목표 시간 5초 이내 달성 확인
- 다양한 기기에서 테스트 완료
앱인토스 특화 권장사항
- 토스페이 연동 대비: 결제 API 사전 초기화
- 사용자 인증 최적화: 토스 로그인 토큰 캐싱
- 메모리 제한 준수: 200MB 이하 유지
- 분석 데이터 활용: 실제 사용자 시작 시간 모니터링
