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

에셋 최적화

앱인토스 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);
        }
    }
}
#endif

2. 오디오 최적화

오디오 압축 및 관리

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}개");
    }
}
#endif

3. 메시 최적화

자동 메시 최적화 시스템

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

에셋 최적화는 앱인토스 게임 성능의 기초예요.
텍스처, 오디오, 메시를 체계적으로 최적화하여 메모리 사용량을 최소화하고 로딩 성능을 극대화하세요.