에셋 최적화
앱인토스 Unity 게임의 모든 에셋을 효율적으로 관리하고 최적화하는 가이드예요.
이 문서를 따라 하면 최상의 성능과 최소한의 메모리 사용량을 함께 달성할 수 있어요.
1. 텍스처 최적화
스마트 텍스처 압축 시스템
c#
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
public class AppsInTossTextureOptimizer : EditorWindow
{
[System.Serializable]
public class TextureOptimizationRule
{
public string folderPath;
public TextureImporterType textureType;
public int maxSize;
public TextureImporterFormat androidFormat;
public TextureImporterFormat iOSFormat;
public TextureImporterFormat webGLFormat;
public bool generateMipMaps;
public FilterMode filterMode;
public TextureWrapMode wrapMode;
public int compressionQuality;
}
[Header("앱인토스 최적화 규칙")]
public List<TextureOptimizationRule> optimizationRules = new List<TextureOptimizationRule>();
[MenuItem("AppsInToss/Texture Optimizer")]
public static void ShowWindow()
{
GetWindow<AppsInTossTextureOptimizer>("Texture Optimizer");
}
void OnEnable()
{
InitializeDefaultRules();
}
void InitializeDefaultRules()
{
optimizationRules.Clear();
// UI 텍스처 규칙
optimizationRules.Add(new TextureOptimizationRule
{
folderPath = "UI",
textureType = TextureImporterType.Sprite,
maxSize = 1024,
androidFormat = TextureImporterFormat.ETC2_RGBA8,
iOSFormat = TextureImporterFormat.ASTC_6x6,
webGLFormat = TextureImporterFormat.DXT5,
generateMipMaps = false,
filterMode = FilterMode.Bilinear,
wrapMode = TextureWrapMode.Clamp,
compressionQuality = 50
});
// 환경 텍스처 규칙
optimizationRules.Add(new TextureOptimizationRule
{
folderPath = "Environment",
textureType = TextureImporterType.Default,
maxSize = 512,
androidFormat = TextureImporterFormat.ETC2_RGB4,
iOSFormat = TextureImporterFormat.ASTC_6x6,
webGLFormat = TextureImporterFormat.DXT1,
generateMipMaps = true,
filterMode = FilterMode.Trilinear,
wrapMode = TextureWrapMode.Repeat,
compressionQuality = 50
});
// 캐릭터 텍스처 규칙
optimizationRules.Add(new TextureOptimizationRule
{
folderPath = "Characters",
textureType = TextureImporterType.Default,
maxSize = 1024,
androidFormat = TextureImporterFormat.ETC2_RGBA8,
iOSFormat = TextureImporterFormat.ASTC_4x4,
webGLFormat = TextureImporterFormat.DXT5,
generateMipMaps = true,
filterMode = FilterMode.Trilinear,
wrapMode = TextureWrapMode.Clamp,
compressionQuality = 75
});
// 앱인토스 브랜딩 텍스처 규칙 (고품질 유지)
optimizationRules.Add(new TextureOptimizationRule
{
folderPath = "AppsInToss",
textureType = TextureImporterType.Sprite,
maxSize = 2048,
androidFormat = TextureImporterFormat.RGBA32,
iOSFormat = TextureImporterFormat.RGBA32,
webGLFormat = TextureImporterFormat.RGBA32,
generateMipMaps = false,
filterMode = FilterMode.Trilinear,
wrapMode = TextureWrapMode.Clamp,
compressionQuality = 100
});
}
void OnGUI()
{
GUILayout.Label("앱인토스 텍스처 최적화", EditorStyles.boldLabel);
GUILayout.Space(10);
// 전체 최적화 버튼
if (GUILayout.Button("모든 텍스처 최적화", GUILayout.Height(30)))
{
OptimizeAllTextures();
}
GUILayout.Space(10);
// 규칙별 최적화
foreach (var rule in optimizationRules)
{
DrawOptimizationRule(rule);
}
GUILayout.Space(10);
// 통계 표시
DrawTextureStatistics();
}
void DrawOptimizationRule(TextureOptimizationRule rule)
{
GUILayout.BeginVertical("box");
GUILayout.Label($"폴더: {rule.folderPath}", EditorStyles.boldLabel);
GUILayout.Label($"최대 크기: {rule.maxSize}px");
GUILayout.Label($"Android: {rule.androidFormat}");
GUILayout.Label($"iOS: {rule.iOSFormat}");
GUILayout.Label($"WebGL: {rule.webGLFormat}");
if (GUILayout.Button($"{rule.folderPath} 폴더 최적화"))
{
OptimizeTexturesInFolder(rule);
}
GUILayout.EndVertical();
GUILayout.Space(5);
}
void OptimizeAllTextures()
{
EditorUtility.DisplayProgressBar("텍스처 최적화", "모든 텍스처를 최적화하는 중...", 0);
try
{
foreach (var rule in optimizationRules)
{
OptimizeTexturesInFolder(rule);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("앱인토스 텍스처 최적화 완료");
ShowNotification(new GUIContent("최적화 완료!"));
}
finally
{
EditorUtility.ClearProgressBar();
}
}
void OptimizeTexturesInFolder(TextureOptimizationRule rule)
{
string folderPath = Path.Combine("Assets", rule.folderPath);
if (!Directory.Exists(folderPath))
{
Debug.LogWarning($"폴더를 찾을 수 없습니다: {folderPath}");
return;
}
string[] textureGuids = AssetDatabase.FindAssets("t:Texture2D", new[] { folderPath });
int optimizedCount = 0;
for (int i = 0; i < textureGuids.Length; i++)
{
string guid = textureGuids[i];
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
EditorUtility.DisplayProgressBar(
$"{rule.folderPath} 텍스처 최적화",
$"{Path.GetFileName(assetPath)}",
(float)i / textureGuids.Length
);
if (OptimizeTexture(assetPath, rule))
{
optimizedCount++;
}
}
Debug.Log($"{rule.folderPath} 폴더 최적화 완료: {optimizedCount}/{textureGuids.Length}개 텍스처");
}
bool OptimizeTexture(string assetPath, TextureOptimizationRule rule)
{
TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
if (importer == null) return false;
bool wasChanged = false;
// 기본 설정
if (importer.textureType != rule.textureType)
{
importer.textureType = rule.textureType;
wasChanged = true;
}
if (importer.mipmapEnabled != rule.generateMipMaps)
{
importer.mipmapEnabled = rule.generateMipMaps;
wasChanged = true;
}
if (importer.filterMode != rule.filterMode)
{
importer.filterMode = rule.filterMode;
wasChanged = true;
}
if (importer.wrapMode != rule.wrapMode)
{
importer.wrapMode = rule.wrapMode;
wasChanged = true;
}
// 플랫폼별 설정
wasChanged |= SetPlatformSettings(importer, "Android", rule.maxSize, rule.androidFormat, rule.compressionQuality);
wasChanged |= SetPlatformSettings(importer, "iPhone", rule.maxSize, rule.iOSFormat, rule.compressionQuality);
wasChanged |= SetPlatformSettings(importer, "WebGL", rule.maxSize, rule.webGLFormat, rule.compressionQuality);
// 앱인토스 특화 설정
if (assetPath.Contains("AppsInToss") || assetPath.Contains("Toss"))
{
importer.userData = "AppsInToss_Asset";
wasChanged = true;
}
if (wasChanged)
{
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
return true;
}
return false;
}
bool SetPlatformSettings(TextureImporter importer, string platform, int maxSize,
TextureImporterFormat format, int quality)
{
var platformSettings = importer.GetPlatformTextureSettings(platform);
bool changed = false;
if (platformSettings.overridden != true)
{
platformSettings.overridden = true;
changed = true;
}
if (platformSettings.maxTextureSize != maxSize)
{
platformSettings.maxTextureSize = maxSize;
changed = true;
}
if (platformSettings.format != format)
{
platformSettings.format = format;
changed = true;
}
if (platformSettings.compressionQuality != quality)
{
platformSettings.compressionQuality = quality;
changed = true;
}
if (changed)
{
importer.SetPlatformTextureSettings(platformSettings);
}
return changed;
}
void DrawTextureStatistics()
{
GUILayout.Label("텍스처 통계", EditorStyles.boldLabel);
var allTextures = AssetDatabase.FindAssets("t:Texture2D");
long totalSize = 0;
int uncompressedCount = 0;
int oversizedCount = 0;
foreach (string guid in allTextures)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (texture != null)
{
totalSize += UnityEngine.Profiling.Profiler.GetRuntimeMemorySizeLong(texture);
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer != null)
{
if (importer.textureCompression == TextureImporterCompression.Uncompressed)
{
uncompressedCount++;
}
if (texture.width > 1024 || texture.height > 1024)
{
oversizedCount++;
}
}
}
}
GUILayout.Label($"전체 텍스처: {allTextures.Length}개");
GUILayout.Label($"총 메모리 사용량: {totalSize / (1024 * 1024)}MB");
GUILayout.Label($"압축되지 않은 텍스처: {uncompressedCount}개", uncompressedCount > 0 ? EditorStyles.boldLabel : EditorStyles.label);
GUILayout.Label($"대형 텍스처 (1024px 초과): {oversizedCount}개", oversizedCount > 0 ? EditorStyles.boldLabel : EditorStyles.label);
if (uncompressedCount > 0 || oversizedCount > 0)
{
EditorGUILayout.HelpBox("일부 텍스처가 최적화되지 않았습니다. 전체 최적화를 실행하세요.", MessageType.Warning);
}
}
}
#endif2. 오디오 최적화
오디오 압축 및 관리
c#
#if UNITY_EDITOR
public class AppsInTossAudioOptimizer : EditorWindow
{
[System.Serializable]
public class AudioOptimizationRule
{
public string folderPath;
public AudioImporterSampleSettings mobileSettings;
public AudioImporterSampleSettings webGLSettings;
public bool force3D;
public bool enableCompression;
public float compressionQuality;
}
public List<AudioOptimizationRule> audioRules = new List<AudioOptimizationRule>();
[MenuItem("AppsInToss/Audio Optimizer")]
public static void ShowWindow()
{
GetWindow<AppsInTossAudioOptimizer>("Audio Optimizer");
}
void OnEnable()
{
InitializeAudioRules();
}
void InitializeAudioRules()
{
audioRules.Clear();
// 음악 파일 규칙
audioRules.Add(new AudioOptimizationRule
{
folderPath = "Audio/Music",
mobileSettings = new AudioImporterSampleSettings
{
loadType = AudioClipLoadType.Streaming,
compressionFormat = AudioCompressionFormat.Vorbis,
quality = 0.7f,
sampleRateSetting = AudioSampleRateSetting.OptimizeForSize
},
webGLSettings = new AudioImporterSampleSettings
{
loadType = AudioClipLoadType.Streaming,
compressionFormat = AudioCompressionFormat.Vorbis,
quality = 0.5f,
sampleRateSetting = AudioSampleRateSetting.OptimizeForSize
},
force3D = false,
enableCompression = true,
compressionQuality = 70f
});
// 효과음 규칙
audioRules.Add(new AudioOptimizationRule
{
folderPath = "Audio/SFX",
mobileSettings = new AudioImporterSampleSettings
{
loadType = AudioClipLoadType.DecompressOnLoad,
compressionFormat = AudioCompressionFormat.ADPCM,
quality = 1.0f,
sampleRateSetting = AudioSampleRateSetting.PreserveSampleRate
},
webGLSettings = new AudioImporterSampleSettings
{
loadType = AudioClipLoadType.CompressedInMemory,
compressionFormat = AudioCompressionFormat.Vorbis,
quality = 0.8f,
sampleRateSetting = AudioSampleRateSetting.OptimizeForSize
},
force3D = false,
enableCompression = true,
compressionQuality = 80f
});
// 앱인토스 특화 오디오 (토스 사운드)
audioRules.Add(new AudioOptimizationRule
{
folderPath = "Audio/Toss",
mobileSettings = new AudioImporterSampleSettings
{
loadType = AudioClipLoadType.DecompressOnLoad,
compressionFormat = AudioCompressionFormat.PCM,
quality = 1.0f,
sampleRateSetting = AudioSampleRateSetting.PreserveSampleRate
},
webGLSettings = new AudioImporterSampleSettings
{
loadType = AudioClipLoadType.DecompressOnLoad,
compressionFormat = AudioCompressionFormat.PCM,
quality = 1.0f,
sampleRateSetting = AudioSampleRateSetting.PreserveSampleRate
},
force3D = false,
enableCompression = false, // 토스 브랜드 오디오는 고품질 유지
compressionQuality = 100f
});
}
void OnGUI()
{
GUILayout.Label("앱인토스 오디오 최적화", EditorStyles.boldLabel);
if (GUILayout.Button("모든 오디오 최적화"))
{
OptimizeAllAudio();
}
foreach (var rule in audioRules)
{
DrawAudioRule(rule);
}
DrawAudioStatistics();
}
void OptimizeAllAudio()
{
foreach (var rule in audioRules)
{
OptimizeAudioInFolder(rule);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("오디오 최적화 완료");
}
void OptimizeAudioInFolder(AudioOptimizationRule rule)
{
string folderPath = Path.Combine("Assets", rule.folderPath);
string[] audioGuids = AssetDatabase.FindAssets("t:AudioClip", new[] { folderPath });
foreach (string guid in audioGuids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
OptimizeAudioClip(assetPath, rule);
}
}
void OptimizeAudioClip(string assetPath, AudioOptimizationRule rule)
{
AudioImporter importer = AssetImporter.GetAtPath(assetPath) as AudioImporter;
if (importer == null) return;
// 모바일 플랫폼 설정
importer.SetOverrideSampleSettings("Android", rule.mobileSettings);
importer.SetOverrideSampleSettings("iOS", rule.mobileSettings);
// WebGL 설정
importer.SetOverrideSampleSettings("WebGL", rule.webGLSettings);
// 3D 설정
if (rule.force3D)
{
importer.threeD = true;
}
AssetDatabase.ImportAsset(assetPath);
}
void DrawAudioRule(AudioOptimizationRule rule)
{
GUILayout.BeginVertical("box");
GUILayout.Label($"폴더: {rule.folderPath}");
GUILayout.Label($"모바일 형식: {rule.mobileSettings.compressionFormat}");
GUILayout.Label($"WebGL 형식: {rule.webGLSettings.compressionFormat}");
if (GUILayout.Button($"{rule.folderPath} 최적화"))
{
OptimizeAudioInFolder(rule);
}
GUILayout.EndVertical();
}
void DrawAudioStatistics()
{
var allAudio = AssetDatabase.FindAssets("t:AudioClip");
long totalSize = 0;
int uncompressedCount = 0;
foreach (string guid in allAudio)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
var clip = AssetDatabase.LoadAssetAtPath<AudioClip>(path);
if (clip != null)
{
totalSize += UnityEngine.Profiling.Profiler.GetRuntimeMemorySizeLong(clip);
var importer = AssetImporter.GetAtPath(path) as AudioImporter;
if (importer?.defaultSampleSettings.compressionFormat == AudioCompressionFormat.PCM)
{
uncompressedCount++;
}
}
}
GUILayout.Label("오디오 통계", EditorStyles.boldLabel);
GUILayout.Label($"전체 오디오 클립: {allAudio.Length}개");
GUILayout.Label($"총 메모리 사용량: {totalSize / (1024 * 1024)}MB");
GUILayout.Label($"압축되지 않은 클립: {uncompressedCount}개");
}
}
#endif3. 메시 최적화
자동 메시 최적화 시스템
c#
#if UNITY_EDITOR
public class AppsInTossMeshOptimizer : EditorWindow
{
[Header("메시 최적화 설정")]
public int maxVertexCount = 1000;
public bool optimizeMeshes = true;
public bool generateLightmapUVs = false;
public bool keepQuads = false;
public bool weldVertices = true;
public float weldingThreshold = 1e-4f;
[Header("LOD 생성")]
public bool generateLODs = true;
public float[] lodReductions = { 0.8f, 0.5f, 0.25f };
public float[] lodDistances = { 10f, 25f, 50f };
[MenuItem("AppsInToss/Mesh Optimizer")]
public static void ShowWindow()
{
GetWindow<AppsInTossMeshOptimizer>("Mesh Optimizer");
}
void OnGUI()
{
GUILayout.Label("앱인토스 메시 최적화", EditorStyles.boldLabel);
maxVertexCount = EditorGUILayout.IntField("최대 버텍스 수", maxVertexCount);
optimizeMeshes = EditorGUILayout.Toggle("메시 최적화", optimizeMeshes);
weldVertices = EditorGUILayout.Toggle("버텍스 결합", weldVertices);
if (weldVertices)
{
weldingThreshold = EditorGUILayout.FloatField("결합 임계값", weldingThreshold);
}
GUILayout.Space(10);
generateLODs = EditorGUILayout.Toggle("LOD 자동 생성", generateLODs);
if (generateLODs)
{
EditorGUILayout.LabelField("LOD 감소율:");
for (int i = 0; i < lodReductions.Length; i++)
{
lodReductions[i] = EditorGUILayout.Slider($"LOD {i+1}", lodReductions[i], 0.1f, 1f);
}
EditorGUILayout.LabelField("LOD 거리:");
for (int i = 0; i < lodDistances.Length; i++)
{
lodDistances[i] = EditorGUILayout.FloatField($"LOD {i+1} 거리", lodDistances[i]);
}
}
GUILayout.Space(10);
if (GUILayout.Button("선택된 메시 최적화"))
{
OptimizeSelectedMeshes();
}
if (GUILayout.Button("모든 메시 최적화"))
{
OptimizeAllMeshes();
}
GUILayout.Space(10);
DrawMeshStatistics();
}
void OptimizeSelectedMeshes()
{
var selection = Selection.gameObjects;
foreach (var go in selection)
{
OptimizeGameObject(go);
}
Debug.Log($"선택된 {selection.Length}개 오브젝트 최적화 완료");
}
void OptimizeAllMeshes()
{
var allMeshFilters = FindObjectsOfType<MeshFilter>();
EditorUtility.DisplayProgressBar("메시 최적화", "모든 메시를 최적화하는 중...", 0);
try
{
for (int i = 0; i < allMeshFilters.Length; i++)
{
EditorUtility.DisplayProgressBar(
"메시 최적화",
$"{allMeshFilters[i].name} 최적화 중...",
(float)i / allMeshFilters.Length
);
OptimizeGameObject(allMeshFilters[i].gameObject);
}
}
finally
{
EditorUtility.ClearProgressBar();
}
Debug.Log($"전체 {allMeshFilters.Length}개 메시 최적화 완료");
}
void OptimizeGameObject(GameObject go)
{
var meshFilter = go.GetComponent<MeshFilter>();
if (meshFilter?.sharedMesh == null) return;
var originalMesh = meshFilter.sharedMesh;
// 버텍스 수 체크
if (originalMesh.vertexCount > maxVertexCount)
{
Debug.LogWarning($"{go.name}: 버텍스 수가 많습니다 ({originalMesh.vertexCount} > {maxVertexCount})");
}
// 메시 최적화
if (optimizeMeshes)
{
OptimizeMesh(originalMesh);
}
// LOD 생성
if (generateLODs && !go.GetComponent<LODGroup>())
{
CreateLODGroup(go);
}
// 앱인토스 특화 최적화
ApplyAppsInTossOptimizations(go);
}
void OptimizeMesh(Mesh mesh)
{
if (weldVertices)
{
MeshUtility.Optimize(mesh);
}
// 법선 벡터 재계산
mesh.RecalculateNormals();
// 바운딩 박스 재계산
mesh.RecalculateBounds();
// 탄젠트 재계산
mesh.RecalculateTangents();
}
void CreateLODGroup(GameObject go)
{
var lodGroup = go.AddComponent<LODGroup>();
var lods = new LOD[lodReductions.Length + 1]; // +1 for original
// 원본 메시 (LOD 0)
var renderers = go.GetComponentsInChildren<Renderer>();
lods[0] = new LOD(1.0f, renderers);
// 감소된 LOD들 생성
for (int i = 0; i < lodReductions.Length; i++)
{
var lodGO = CreateReducedMesh(go, lodReductions[i], i + 1);
if (lodGO != null)
{
float screenHeight = 1.0f / (lodDistances[i] / 10f);
lods[i + 1] = new LOD(screenHeight, lodGO.GetComponentsInChildren<Renderer>());
}
}
lodGroup.SetLODs(lods);
}
GameObject CreateReducedMesh(GameObject original, float reduction, int lodLevel)
{
var meshFilter = original.GetComponent<MeshFilter>();
if (meshFilter?.sharedMesh == null) return null;
// 간단한 메시 감소 (실제로는 더 정교한 알고리즘 필요)
var lodGO = new GameObject($"{original.name}_LOD{lodLevel}");
lodGO.transform.parent = original.transform.parent;
lodGO.transform.localPosition = original.transform.localPosition;
lodGO.transform.localRotation = original.transform.localRotation;
lodGO.transform.localScale = original.transform.localScale;
var lodMeshFilter = lodGO.AddComponent<MeshFilter>();
var lodRenderer = lodGO.AddComponent<MeshRenderer>();
// 간소화된 메시 생성
var simplifiedMesh = CreateSimplifiedMesh(meshFilter.sharedMesh, reduction);
lodMeshFilter.sharedMesh = simplifiedMesh;
lodRenderer.sharedMaterial = original.GetComponent<MeshRenderer>().sharedMaterial;
return lodGO;
}
Mesh CreateSimplifiedMesh(Mesh originalMesh, float reduction)
{
// 간단한 버텍스 간소화 (실제로는 Mesh Simplification 라이브러리 사용 권장)
var vertices = originalMesh.vertices;
var triangles = originalMesh.triangles;
var normals = originalMesh.normals;
var uvs = originalMesh.uv;
int targetVertexCount = Mathf.RoundToInt(vertices.Length * reduction);
if (targetVertexCount >= vertices.Length) return originalMesh;
// 단순한 버텍스 샘플링 (실제로는 더 정교한 간소화 필요)
var step = vertices.Length / targetVertexCount;
var newVertices = new List<Vector3>();
var newNormals = new List<Vector3>();
var newUVs = new List<Vector2>();
for (int i = 0; i < vertices.Length; i += step)
{
newVertices.Add(vertices[i]);
if (i < normals.Length) newNormals.Add(normals[i]);
if (i < uvs.Length) newUVs.Add(uvs[i]);
}
var simplifiedMesh = new Mesh();
simplifiedMesh.vertices = newVertices.ToArray();
simplifiedMesh.normals = newNormals.ToArray();
simplifiedMesh.uv = newUVs.ToArray();
// 삼각형 재구성 (매우 간단한 버전)
var newTriangles = new List<int>();
for (int i = 0; i < newVertices.Count - 2; i++)
{
newTriangles.Add(i);
newTriangles.Add(i + 1);
newTriangles.Add(i + 2);
}
simplifiedMesh.triangles = newTriangles.ToArray();
simplifiedMesh.RecalculateBounds();
return simplifiedMesh;
}
void ApplyAppsInTossOptimizations(GameObject go)
{
// 앱인토스 특화 최적화 태그 추가
if (go.name.Contains("Toss") || go.name.Contains("AppsInToss"))
{
go.tag = "AppsInTossAsset";
}
// 모바일 최적화 설정
var meshRenderer = go.GetComponent<MeshRenderer>();
if (meshRenderer != null)
{
// 라이트맵 설정 최적화
meshRenderer.lightmapIndex = -1;
meshRenderer.realtimeLightmapIndex = -1;
// 그림자 최적화
if (go.name.Contains("Background") || go.name.Contains("Environment"))
{
meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
}
}
}
void DrawMeshStatistics()
{
GUILayout.Label("메시 통계", EditorStyles.boldLabel);
var allMeshFilters = FindObjectsOfType<MeshFilter>();
int totalVertices = 0;
int totalTriangles = 0;
int highPolyCount = 0;
long totalMemory = 0;
foreach (var mf in allMeshFilters)
{
if (mf.sharedMesh != null)
{
totalVertices += mf.sharedMesh.vertexCount;
totalTriangles += mf.sharedMesh.triangles.Length / 3;
totalMemory += UnityEngine.Profiling.Profiler.GetRuntimeMemorySizeLong(mf.sharedMesh);
if (mf.sharedMesh.vertexCount > maxVertexCount)
{
highPolyCount++;
}
}
}
GUILayout.Label($"총 메시 수: {allMeshFilters.Length}");
GUILayout.Label($"총 버텍스: {totalVertices:N0}");
GUILayout.Label($"총 삼각형: {totalTriangles:N0}");
GUILayout.Label($"메모리 사용량: {totalMemory / (1024 * 1024)}MB");
if (highPolyCount > 0)
{
GUILayout.Label($"고폴리곤 메시: {highPolyCount}개", EditorStyles.boldLabel);
EditorGUILayout.HelpBox($"{highPolyCount}개의 메시가 {maxVertexCount} 버텍스를 초과합니다.", MessageType.Warning);
}
}
}
#endif에셋 최적화는 앱인토스 게임 성능의 기초예요.
텍스처, 오디오, 메시를 체계적으로 최적화하여 메모리 사용량을 최소화하고 로딩 성능을 극대화하세요.
