리소스 로딩 최적화
앱인토스 Unity 게임에서 리소스를 효율적으로 로딩하고 관리하여 메모리 사용량을 최소화하고 성능을 극대화하는 방법을 제공해요.
1. 리소스 로딩 전략
앱인토스 리소스 관리 원칙
📦 앱인토스 리소스 관리 전략
├── 초기 번들 (Critical Bundle) - 5MB 이내
│ ├── 게임 엔진 코어
│ ├── 앱인토스 SDK
│ ├── 첫 화면 UI
│ └── 필수 폰트/아이콘
├── 기능별 번들 (Feature Bundles) - 각 10MB 이내
│ ├── 게임플레이 에셋
│ ├── UI 시스템
│ ├── 오디오 에셋
│ └── 이펙트 시스템
├── 토스 연동 번들 (Toss Integration) - 3MB 이내
│ ├── 토스페이 UI
│ ├── 토스 로그인 리소스
│ └── 분석 시스템
└── 온디맨드 번들 (On-Demand) - 유연한 크기
├── 레벨별 에셋
├── 캐릭터 스킨
└── 계절 이벤트 리소스리소스 분류 시스템
c#
using UnityEngine;
using UnityEngine.AddressableAssets;
using System.Collections.Generic;
using System.Linq;
[System.Serializable]
public class ResourceCategory
{
public string categoryName;
public ResourcePriority priority;
public ResourceType resourceType;
public List<string> assetLabels;
public long maxCacheSizeMB = 50;
public float cacheTimeoutMinutes = 30;
public bool compressOnDownload = true;
public bool enableStreaming = false;
}
public enum ResourcePriority
{
Critical = 0, // 즉시 필요한 리소스
High = 1, // 곧 사용될 리소스
Medium = 2, // 백그라운드 로딩
Low = 3, // 필요시 로딩
Toss = 4 // 앱인토스 특화 리소스
}
public enum ResourceType
{
Texture,
Audio,
Model,
Animation,
UI,
Script,
Font,
Shader,
TossIntegration // 앱인토스 연동 전용
}
public class AppsInTossResourceManager : MonoBehaviour
{
public static AppsInTossResourceManager Instance { get; private set; }
[Header("리소스 카테고리")]
public ResourceCategory[] categories;
[Header("앱인토스 설정")]
public long totalMemoryLimitMB = 200; // 앱인토스 메모리 제한
public bool enableTossAnalytics = true;
public bool optimizeForMobile = true;
// 리소스 캐시 관리
private Dictionary<string, CachedResource> resourceCache = new Dictionary<string, CachedResource>();
private Dictionary<string, ResourceCategory> categoryMap = new Dictionary<string, ResourceCategory>();
private Queue<string> loadingQueue = new Queue<string>();
[System.Serializable]
private class CachedResource
{
public object resource;
public ResourceCategory category;
public System.DateTime loadTime;
public System.DateTime lastAccessTime;
public int accessCount;
public long memorySizeBytes;
public UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle handle;
}
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
InitializeResourceManager();
}
else
{
Destroy(gameObject);
}
}
void InitializeResourceManager()
{
Debug.Log("앱인토스 리소스 매니저 초기화");
// 카테고리 맵 생성
foreach (var category in categories)
{
categoryMap[category.categoryName] = category;
}
// 메모리 모니터링 시작
InvokeRepeating(nameof(MonitorMemoryUsage), 10f, 10f);
// 앱인토스 특화 리소스 사전 로딩
StartCoroutine(PreloadTossResources());
}
// 리소스 로딩 API
public void LoadResourceAsync<T>(string address, System.Action<T> onComplete,
System.Action<string> onError = null) where T : UnityEngine.Object
{
StartCoroutine(LoadResourceCoroutine<T>(address, onComplete, onError));
}
System.Collections.IEnumerator LoadResourceCoroutine<T>(string address,
System.Action<T> onComplete, System.Action<string> onError) where T : UnityEngine.Object
{
// 캐시 확인
if (resourceCache.ContainsKey(address))
{
var cached = resourceCache[address];
cached.lastAccessTime = System.DateTime.UtcNow;
cached.accessCount++;
onComplete?.Invoke(cached.resource as T);
yield break;
}
Debug.Log($"리소스 로딩 시작: {address}");
float startTime = Time.realtimeSinceStartup;
// Addressable 에셋 로딩
var handle = Addressables.LoadAssetAsync<T>(address);
yield return handle;
if (handle.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
// 캐시에 추가
var category = DetermineResourceCategory(address, typeof(T));
var cached = new CachedResource
{
resource = handle.Result,
category = category,
loadTime = System.DateTime.UtcNow,
lastAccessTime = System.DateTime.UtcNow,
accessCount = 1,
memorySizeBytes = EstimateResourceSize(handle.Result),
handle = handle
};
resourceCache[address] = cached;
float loadTime = Time.realtimeSinceStartup - startTime;
Debug.Log($"리소스 로딩 완료: {address} ({loadTime:F2}초)");
// 앱인토스 분석에 로딩 데이터 전송
if (enableTossAnalytics)
{
SendResourceLoadingAnalytics(address, loadTime, cached.memorySizeBytes, true);
}
onComplete?.Invoke(handle.Result);
}
else
{
string error = $"리소스 로딩 실패: {address} - {handle.OperationException}";
Debug.LogError(error);
if (enableTossAnalytics)
{
SendResourceLoadingAnalytics(address, -1, 0, false);
}
onError?.Invoke(error);
}
}
ResourceCategory DetermineResourceCategory(string address, System.Type resourceType)
{
// 주소와 타입 기반으로 카테고리 결정
if (address.Contains("Toss") || address.Contains("AIT"))
{
return categoryMap.ContainsKey("TossIntegration") ?
categoryMap["TossIntegration"] : categories.FirstOrDefault();
}
foreach (var category in categories)
{
foreach (var label in category.assetLabels)
{
if (address.Contains(label))
{
return category;
}
}
}
return categories.FirstOrDefault(); // 기본 카테고리
}
long EstimateResourceSize(UnityEngine.Object resource)
{
if (resource is Texture2D texture)
{
return texture.width * texture.height * 4; // RGBA32 추정
}
else if (resource is AudioClip audio)
{
return audio.samples * audio.channels * 2; // 16-bit PCM 추정
}
else if (resource is Mesh mesh)
{
return mesh.vertexCount * 32; // 추정값
}
else if (resource is GameObject go)
{
// GameObject의 컴포넌트들을 기반으로 추정
return EstimateGameObjectSize(go);
}
return 1024 * 1024; // 1MB 기본값
}
long EstimateGameObjectSize(GameObject go)
{
long totalSize = 0;
// 메시 렌더러
var meshRenderers = go.GetComponentsInChildren<MeshRenderer>();
foreach (var mr in meshRenderers)
{
var meshFilter = mr.GetComponent<MeshFilter>();
if (meshFilter != null && meshFilter.mesh != null)
{
totalSize += meshFilter.mesh.vertexCount * 32;
}
}
// 텍스처
var renderers = go.GetComponentsInChildren<Renderer>();
foreach (var renderer in renderers)
{
foreach (var material in renderer.materials)
{
if (material.mainTexture is Texture2D tex)
{
totalSize += tex.width * tex.height * 4;
}
}
}
return totalSize;
}
// 배치 로딩
public void LoadResourcesBatch<T>(List<string> addresses,
System.Action<Dictionary<string, T>> onComplete,
System.Action<float> onProgress = null) where T : UnityEngine.Object
{
StartCoroutine(LoadResourcesBatchCoroutine<T>(addresses, onComplete, onProgress));
}
System.Collections.IEnumerator LoadResourcesBatchCoroutine<T>(List<string> addresses,
System.Action<Dictionary<string, T>> onComplete,
System.Action<float> onProgress) where T : UnityEngine.Object
{
var results = new Dictionary<string, T>();
int loadedCount = 0;
foreach (var address in addresses)
{
bool loadCompleted = false;
T loadedResource = null;
LoadResourceAsync<T>(address,
(resource) => {
loadedResource = resource;
loadCompleted = true;
},
(error) => {
loadCompleted = true;
}
);
yield return new WaitUntil(() => loadCompleted);
if (loadedResource != null)
{
results[address] = loadedResource;
}
loadedCount++;
float progress = (float)loadedCount / addresses.Count;
onProgress?.Invoke(progress);
}
onComplete?.Invoke(results);
}
void MonitorMemoryUsage()
{
long totalMemoryBytes = 0;
var expiredResources = new List<string>();
foreach (var kvp in resourceCache)
{
var cached = kvp.Value;
totalMemoryBytes += cached.memorySizeBytes;
// 만료된 리소스 확인
var timeSinceLastAccess = System.DateTime.UtcNow - cached.lastAccessTime;
if (timeSinceLastAccess.TotalMinutes > cached.category.cacheTimeoutMinutes)
{
expiredResources.Add(kvp.Key);
}
}
long totalMemoryMB = totalMemoryBytes / (1024 * 1024);
// 메모리 제한 체크
if (totalMemoryMB > totalMemoryLimitMB)
{
Debug.LogWarning($"리소스 메모리 사용량 초과: {totalMemoryMB}MB > {totalMemoryLimitMB}MB");
OptimizeMemoryUsage();
}
// 만료된 리소스 정리
foreach (var address in expiredResources)
{
UnloadResource(address);
}
if (enableTossAnalytics && totalMemoryMB > 0)
{
SendMemoryUsageAnalytics(totalMemoryMB, resourceCache.Count);
}
}
void OptimizeMemoryUsage()
{
Debug.Log("리소스 메모리 최적화 시작");
// 우선순위와 사용 빈도 기반으로 정렬
var sortedResources = resourceCache.ToList();
sortedResources.Sort((a, b) => {
var scoreA = CalculateResourceScore(a.Value);
var scoreB = CalculateResourceScore(b.Value);
return scoreA.CompareTo(scoreB);
});
// 하위 30% 리소스 언로드
int resourcesToUnload = Mathf.CeilToInt(sortedResources.Count * 0.3f);
for (int i = 0; i < resourcesToUnload && i < sortedResources.Count; i++)
{
var address = sortedResources[i].Key;
var cached = sortedResources[i].Value;
// Critical 우선순위는 보호
if (cached.category.priority != ResourcePriority.Critical)
{
UnloadResource(address);
}
}
Debug.Log($"메모리 최적화 완료: {resourcesToUnload}개 리소스 언로드");
}
float CalculateResourceScore(CachedResource cached)
{
// 점수가 낮을수록 언로드 우선순위 높음
float score = 0f;
// 우선순위 점수 (낮을수록 중요)
score += (int)cached.category.priority * 10f;
// 사용 빈도 점수
score += cached.accessCount * 5f;
// 최근 사용 시간 점수
var timeSinceAccess = (System.DateTime.UtcNow - cached.lastAccessTime).TotalMinutes;
score -= (float)timeSinceAccess * 0.1f;
// 메모리 크기 페널티
var sizeMB = cached.memorySizeBytes / (1024f * 1024f);
score -= sizeMB * 2f;
return score;
}
public void UnloadResource(string address)
{
if (resourceCache.ContainsKey(address))
{
var cached = resourceCache[address];
// Addressables 핸들 해제
if (cached.handle.IsValid())
{
Addressables.Release(cached.handle);
}
resourceCache.Remove(address);
Debug.Log($"리소스 언로드: {address}");
}
}
// 앱인토스 특화 기능
System.Collections.IEnumerator PreloadTossResources()
{
Debug.Log("앱인토스 특화 리소스 사전 로딩 시작");
var tossResources = new List<string>
{
"TossPayIcon",
"TossLogo",
"TossButton",
"TossNotification",
"TossFont",
"TossColorPalette"
};
foreach (var resource in tossResources)
{
LoadResourceAsync<UnityEngine.Object>(resource,
(loaded) => {
Debug.Log($"앱인토스 리소스 로딩 완료: {resource}");
},
(error) => {
Debug.LogWarning($"앱인토스 리소스 로딩 실패: {resource} - {error}");
}
);
yield return new WaitForSeconds(0.1f); // 부하 분산
}
Debug.Log("앱인토스 특화 리소스 사전 로딩 완료");
}
public void PreloadTossPayResources(System.Action onComplete = null)
{
StartCoroutine(PreloadTossPayResourcesCoroutine(onComplete));
}
System.Collections.IEnumerator PreloadTossPayResourcesCoroutine(System.Action onComplete)
{
var tossPayResources = new List<string>
{
"TossPayUI",
"PaymentMethods",
"ReceiptTemplate",
"PaymentSuccessEffect",
"PaymentFailureEffect"
};
yield return StartCoroutine(LoadResourcesBatchCoroutine<UnityEngine.Object>(
tossPayResources,
(results) => {
Debug.Log($"토스페이 리소스 로딩 완료: {results.Count}/{tossPayResources.Count}");
onComplete?.Invoke();
}
));
}
void SendResourceLoadingAnalytics(string address, float loadTime, long memorySize, bool success)
{
var analyticsData = new Dictionary<string, object>
{
{"resource_address", address},
{"load_time", loadTime},
{"memory_size_mb", memorySize / (1024f * 1024f)},
{"success", success},
{"timestamp", System.DateTime.UtcNow.ToString("o")}
};
AppsInToss.SendAnalytics("resource_loading", analyticsData);
}
void SendMemoryUsageAnalytics(long memoryUsageMB, int cachedResourceCount)
{
var analyticsData = new Dictionary<string, object>
{
{"memory_usage_mb", memoryUsageMB},
{"cached_resources", cachedResourceCount},
{"memory_limit_mb", totalMemoryLimitMB},
{"timestamp", System.DateTime.UtcNow.ToString("o")}
};
AppsInToss.SendAnalytics("memory_usage", analyticsData);
}
// 공개 API
public bool IsResourceCached(string address)
{
return resourceCache.ContainsKey(address);
}
public void ClearCache(ResourcePriority maxPriority = ResourcePriority.Low)
{
var addressesToRemove = new List<string>();
foreach (var kvp in resourceCache)
{
if (kvp.Value.category.priority >= maxPriority)
{
addressesToRemove.Add(kvp.Key);
}
}
foreach (var address in addressesToRemove)
{
UnloadResource(address);
}
Debug.Log($"캐시 정리 완료: {addressesToRemove.Count}개 리소스 제거");
}
public ResourceLoadingReport GenerateLoadingReport()
{
var report = new ResourceLoadingReport();
report.totalCachedResources = resourceCache.Count;
report.totalMemoryUsageMB = resourceCache.Values.Sum(r => r.memorySizeBytes) / (1024f * 1024f);
report.memoryLimitMB = totalMemoryLimitMB;
// 카테고리별 통계
report.categoryStats = categories.Select(category => {
var categoryResources = resourceCache.Values.Where(r => r.category == category);
return new ResourceCategoryStats
{
categoryName = category.categoryName,
resourceCount = categoryResources.Count(),
memoryUsageMB = categoryResources.Sum(r => r.memorySizeBytes) / (1024f * 1024f),
averageAccessCount = categoryResources.Any() ?
categoryResources.Average(r => r.accessCount) : 0f
};
}).ToList();
return report;
}
}
[System.Serializable]
public class ResourceLoadingReport
{
public int totalCachedResources;
public float totalMemoryUsageMB;
public float memoryLimitMB;
public List<ResourceCategoryStats> categoryStats;
}
[System.Serializable]
public class ResourceCategoryStats
{
public string categoryName;
public int resourceCount;
public float memoryUsageMB;
public float averageAccessCount;
}2. 텍스처 최적화
모바일 최적화 텍스처 관리
c#
public class MobileTextureOptimizer : MonoBehaviour
{
[System.Serializable]
public class TextureQualitySettings
{
public string qualityLevel;
public int maxTextureSize;
public TextureFormat preferredFormat;
public bool enableMipMaps;
public FilterMode filterMode;
public int compressionQuality;
}
[Header("기기별 텍스처 설정")]
public TextureQualitySettings[] qualityTiers;
[Header("앱인토스 최적화")]
public bool enableDynamicQuality = true;
public bool adaptToMemoryPressure = true;
public long lowMemoryThresholdMB = 150;
private TextureQualitySettings currentQuality;
void Start()
{
DetermineOptimalQuality();
if (adaptToMemoryPressure)
{
InvokeRepeating(nameof(CheckMemoryPressure), 30f, 30f);
}
}
void DetermineOptimalQuality()
{
// 기기 성능에 따른 품질 결정
int deviceTier = GetDevicePerformanceTier();
if (deviceTier < qualityTiers.Length)
{
currentQuality = qualityTiers[deviceTier];
ApplyTextureQuality(currentQuality);
}
Debug.Log($"텍스처 품질 설정: {currentQuality.qualityLevel}");
}
int GetDevicePerformanceTier()
{
// 메모리 크기 기반 기기 성능 분류
int memoryGB = SystemInfo.systemMemorySize / 1024;
if (memoryGB >= 8) return 0; // 고사양
else if (memoryGB >= 4) return 1; // 중사양
else if (memoryGB >= 2) return 2; // 저사양
else return 3; // 초저사양
}
void ApplyTextureQuality(TextureQualitySettings quality)
{
QualitySettings.masterTextureLimit = GetTextureLimitFromSize(quality.maxTextureSize);
QualitySettings.anisotropicFiltering = quality.filterMode == FilterMode.Trilinear ?
AnisotropicFiltering.Enable : AnisotropicFiltering.Disable;
Debug.Log($"텍스처 품질 적용: 최대 크기 {quality.maxTextureSize}, 포맷 {quality.preferredFormat}");
}
int GetTextureLimitFromSize(int maxSize)
{
// Unity의 masterTextureLimit은 반대 방향 (0=원본, 1=1/2, 2=1/4)
if (maxSize >= 2048) return 0;
else if (maxSize >= 1024) return 1;
else if (maxSize >= 512) return 2;
else return 3;
}
void CheckMemoryPressure()
{
long currentMemoryMB = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemory(false) / (1024 * 1024);
if (currentMemoryMB > lowMemoryThresholdMB && enableDynamicQuality)
{
// 메모리 압박 시 텍스처 품질 하향 조정
ReduceTextureQuality();
}
}
void ReduceTextureQuality()
{
int currentTier = System.Array.IndexOf(qualityTiers, currentQuality);
if (currentTier < qualityTiers.Length - 1)
{
currentQuality = qualityTiers[currentTier + 1];
ApplyTextureQuality(currentQuality);
Debug.LogWarning($"메모리 압박으로 텍스처 품질 하향: {currentQuality.qualityLevel}");
// 앱인토스에 성능 이슈 리포트
AppsInToss.ReportPerformanceIssue("texture_quality_reduced", currentQuality.qualityLevel);
}
}
// 런타임 텍스처 압축
public void CompressTexture(Texture2D texture, bool highQuality = false)
{
if (texture == null || !texture.isReadable) return;
TextureFormat targetFormat = currentQuality.preferredFormat;
// 앱인토스 환경에 최적화된 포맷 선택
if (SystemInfo.SupportsTextureFormat(TextureFormat.ASTC_6x6))
{
targetFormat = highQuality ? TextureFormat.ASTC_4x4 : TextureFormat.ASTC_6x6;
}
else if (SystemInfo.SupportsTextureFormat(TextureFormat.ETC2_RGBA8))
{
targetFormat = TextureFormat.ETC2_RGBA8;
}
// 텍스처 압축 적용
texture.Compress(highQuality);
Debug.Log($"텍스처 압축 완료: {texture.name} -> {targetFormat}");
}
}3. 오디오 리소스 최적화
효율적인 오디오 관리
c#
public class AudioResourceManager : MonoBehaviour
{
[System.Serializable]
public class AudioClipData
{
public string clipName;
public AudioType audioType;
public AudioClipLoadType loadType;
public bool compress;
public float quality = 0.5f;
public bool loop;
}
public enum AudioType
{
Music,
SFX,
Voice,
UI,
TossNotification // 앱인토스 특화
}
[Header("오디오 클립 설정")]
public AudioClipData[] audioClips;
[Header("앱인토스 오디오 설정")]
public bool enableTossAudio = true;
public bool optimizeForMobile = true;
public int maxConcurrentAudioSources = 8;
private Dictionary<string, AudioClip> loadedClips = new Dictionary<string, AudioClip>();
private Dictionary<AudioType, float> typeVolumeSettings = new Dictionary<AudioType, float>();
private Queue<AudioSource> audioSourcePool = new Queue<AudioSource>();
void Start()
{
InitializeAudioSettings();
CreateAudioSourcePool();
if (enableTossAudio)
{
LoadTossAudioResources();
}
}
void InitializeAudioSettings()
{
// 오디오 타입별 기본 볼륨 설정
typeVolumeSettings[AudioType.Music] = 0.7f;
typeVolumeSettings[AudioType.SFX] = 0.8f;
typeVolumeSettings[AudioType.Voice] = 1.0f;
typeVolumeSettings[AudioType.UI] = 0.6f;
typeVolumeSettings[AudioType.TossNotification] = 0.9f; // 토스 알림음
// 모바일 최적화 설정
if (optimizeForMobile)
{
AudioSettings.GetConfiguration(out var config);
config.numVirtualVoices = 256;
config.numRealVoices = 32;
AudioSettings.Reset(config);
}
}
void CreateAudioSourcePool()
{
for (int i = 0; i < maxConcurrentAudioSources; i++)
{
var audioSourceGO = new GameObject($"AudioSource_{i}");
audioSourceGO.transform.parent = transform;
var audioSource = audioSourceGO.AddComponent<AudioSource>();
audioSourcePool.Enqueue(audioSource);
}
}
void LoadTossAudioResources()
{
var tossAudioResources = new List<string>
{
"TossPaySuccess",
"TossPayFailure",
"TossNotification",
"TossButtonClick",
"TossSwipe"
};
foreach (var resource in tossAudioResources)
{
AppsInTossResourceManager.Instance.LoadResourceAsync<AudioClip>(resource,
(clip) => {
loadedClips[resource] = clip;
Debug.Log($"앱인토스 오디오 리소스 로딩 완료: {resource}");
},
(error) => {
Debug.LogWarning($"앱인토스 오디오 리소스 로딩 실패: {resource}");
}
);
}
}
public void PlayAudio(string clipName, AudioType audioType, float volume = -1f)
{
if (!loadedClips.ContainsKey(clipName))
{
// 동적 로딩
AppsInTossResourceManager.Instance.LoadResourceAsync<AudioClip>(clipName,
(clip) => {
loadedClips[clipName] = clip;
PlayLoadedAudio(clipName, audioType, volume);
}
);
return;
}
PlayLoadedAudio(clipName, audioType, volume);
}
void PlayLoadedAudio(string clipName, AudioType audioType, float volume)
{
if (!loadedClips.ContainsKey(clipName)) return;
var audioSource = GetAudioSource();
if (audioSource == null)
{
Debug.LogWarning("사용 가능한 AudioSource가 없습니다");
return;
}
audioSource.clip = loadedClips[clipName];
audioSource.volume = volume >= 0 ? volume : typeVolumeSettings[audioType];
audioSource.loop = GetAudioClipData(clipName)?.loop ?? false;
audioSource.Play();
// 재생 완료 후 AudioSource 반환
StartCoroutine(ReturnAudioSourceAfterPlay(audioSource));
}
AudioSource GetAudioSource()
{
if (audioSourcePool.Count > 0)
{
return audioSourcePool.Dequeue();
}
// 풀이 비어있으면 새로 생성
var audioSourceGO = new GameObject($"AudioSource_Dynamic");
audioSourceGO.transform.parent = transform;
return audioSourceGO.AddComponent<AudioSource>();
}
System.Collections.IEnumerator ReturnAudioSourceAfterPlay(AudioSource audioSource)
{
yield return new WaitUntil(() => !audioSource.isPlaying);
audioSource.clip = null;
audioSourcePool.Enqueue(audioSource);
}
AudioClipData GetAudioClipData(string clipName)
{
return System.Array.Find(audioClips, clip => clip.clipName == clipName);
}
// 앱인토스 특화 오디오 기능
public void PlayTossNotification()
{
PlayAudio("TossNotification", AudioType.TossNotification);
// 진동과 함께 재생 (앱인토스 네이티브 기능)
AppsInToss.TriggerHapticFeedback(AppsInToss.HapticType.Notification);
}
public void PlayTossPaySound(bool success)
{
string clipName = success ? "TossPaySuccess" : "TossPayFailure";
PlayAudio(clipName, AudioType.TossNotification);
// 결제 결과에 따른 햅틱 피드백
var hapticType = success ? AppsInToss.HapticType.Success : AppsInToss.HapticType.Error;
AppsInToss.TriggerHapticFeedback(hapticType);
}
public void SetVolumeForType(AudioType audioType, float volume)
{
typeVolumeSettings[audioType] = Mathf.Clamp01(volume);
// 현재 재생 중인 해당 타입의 오디오 볼륨 조정
foreach (var audioSource in FindObjectsOfType<AudioSource>())
{
if (audioSource.clip != null)
{
var clipData = GetAudioClipData(audioSource.clip.name);
if (clipData != null && clipData.audioType == audioType)
{
audioSource.volume = volume;
}
}
}
}
public void StopAllAudio(AudioType? specificType = null)
{
foreach (var audioSource in FindObjectsOfType<AudioSource>())
{
if (specificType.HasValue)
{
var clipData = GetAudioClipData(audioSource.clip?.name);
if (clipData?.audioType == specificType.Value)
{
audioSource.Stop();
}
}
else
{
audioSource.Stop();
}
}
}
// 메모리 최적화
public void UnloadUnusedAudioClips()
{
var clipsToRemove = new List<string>();
foreach (var kvp in loadedClips)
{
bool isPlaying = false;
foreach (var audioSource in FindObjectsOfType<AudioSource>())
{
if (audioSource.clip == kvp.Value && audioSource.isPlaying)
{
isPlaying = true;
break;
}
}
if (!isPlaying)
{
clipsToRemove.Add(kvp.Key);
}
}
foreach (var clipName in clipsToRemove)
{
loadedClips.Remove(clipName);
}
Resources.UnloadUnusedAssets();
Debug.Log($"사용하지 않는 오디오 클립 언로드: {clipsToRemove.Count}개");
}
}4. 체크리스트 및 권장사항
리소스 로딩 최적화 체크리스트
- 리소스 카테고리 시스템 구현
- 우선순위 기반 로딩 시스템 적용
- 메모리 사용량 모니터링 시스템
- 텍스처 최적화 시스템 적용
- 오디오 리소스 관리 시스템
- 동적 품질 조정 시스템
- 캐시 관리 및 정리 시스템
- 앱인토스 특화 리소스 사전 로딩
- 성능 분석 및 리포팅 시스템
- 메모리 압박 상황 대응 시스템
앱인토스 특화 권장사항
- 메모리 제한 준수: 총 200MB 이내 메모리 사용
- 토스 브랜딩 에셋: 토스 디자인 시스템 리소스 우선 로딩
- 토스페이 연동: 결제 관련 리소스 사전 준비
- 모바일 최적화: 기기 성능에 따른 동적 품질 조정
- 분석 연동: 리소스 사용량 데이터 앱인토스 분석 시스템 전송
효율적인 리소스 관리는 성능과 사용자 경험의 핵심이에요. 앱인토스 환경에 최적화된 리소스 전략을 수립하세요.
