using System; using System.IO; using System.Text; using System.Collections.Generic; using UnityEditor; using UnityEngine; using ZEPETO.Asset; using UnityMeshSimplifier; // Pastikan library ada di folder Assets public class TestBuilder { public static void ManualConvert() { EditorApplication.LockReloadAssemblies(); Debug.Log("==============================================================="); Debug.Log("🛠️ [ZEPETO BUILDER] Smart Minifier & Texture (Original Material Guard)"); Debug.Log("==============================================================="); try { // --- KUNCI PENYELESAIAN FATAL ERROR (CRASH) --- string[] badPackages = Directory.GetFiles("Assets", "package.json", SearchOption.AllDirectories); foreach (string badPkg in badPackages) { File.Delete(badPkg); if (File.Exists(badPkg + ".meta")) File.Delete(badPkg + ".meta"); Debug.Log($"🗑️ [FIX] Menghapus package.json nyasar: {badPkg}"); } // 1. Parsing Argumen Command Line string[] args = Environment.GetCommandLineArgs(); string z_id = ""; string z_pw = ""; int targetTris = 9900; for (int i = 0; i < args.Length; i++) { if (args[i] == "-id" && i + 1 < args.Length) z_id = args[i + 1]; if (args[i] == "-password" && i + 1 < args.Length) z_pw = args[i + 1]; if (args[i] == "-maxtris" && i + 1 < args.Length) targetTris = int.Parse(args[i + 1]); } // 2. Tentukan Path string inputPath = "Assets/InputRaw"; string outputPath = "Assets/OptimizedOutput"; if (!Directory.Exists(inputPath)) { Debug.LogError($"❌ [BUILDER] Folder tidak ditemukan: {inputPath}"); EditorApplication.UnlockReloadAssemblies(); EditorApplication.Exit(1); return; } string[] prefabs = Directory.GetFiles(inputPath, "*.prefab", SearchOption.AllDirectories); if (prefabs.Length == 0) { Debug.LogError("❌ [BUILDER] Tidak ada file .prefab ditemukan."); EditorApplication.UnlockReloadAssemblies(); EditorApplication.Exit(1); return; } string targetPrefabPath = prefabs[0]; string prefabFileName = Path.GetFileName(targetPrefabPath); // 3. Buat Folder Output yang Bersih if (Directory.Exists(outputPath)) Directory.Delete(outputPath, true); Directory.CreateDirectory(outputPath); AssetDatabase.Refresh(); // 4. Muat Prefab Asli dan Instansiasi ke Memori Scene GameObject originalPrefab = AssetDatabase.LoadAssetAtPath(targetPrefabPath); GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(originalPrefab); // ==================================================================== // --- TAHAP PERSIAPAN MEGA PREFAB (WADAH) --- // ==================================================================== string finalPrefabPath = $"{outputPath}/{prefabFileName}"; GameObject tempObj = new GameObject("TempContainer"); PrefabUtility.SaveAsPrefabAsset(tempObj, finalPrefabPath); UnityEngine.Object.DestroyImmediate(tempObj); // ==================================================================== // --- TAHAP SMART TEXTURE SCALER (Target Max 950 KB) --- // ==================================================================== Debug.Log("🖼️ [TEXTURE] Memulai Kalkulasi Smart Texture Scaler (Target: 950KB)..."); var allRenderers = instance.GetComponentsInChildren(true); HashSet uniqueTextures = new HashSet(); foreach (var rend in allRenderers) { foreach (var mat in rend.sharedMaterials) { if (mat == null) continue; string[] texNames = mat.GetTexturePropertyNames(); foreach (var tName in texNames) { Texture tex = mat.GetTexture(tName); if (tex is Texture2D t2d) uniqueTextures.Add(t2d); } } } long totalPixels = 0; foreach (var tex in uniqueTextures) { totalPixels += (tex.width * tex.height); } long maxSafePixels = 950 * 1024; float textureScaleRatio = 1.0f; if (totalPixels > maxSafePixels) { textureScaleRatio = Mathf.Sqrt((float)maxSafePixels / (float)totalPixels); Debug.Log($"📉 [TEXTURE MATH] Menerapkan Smart Scale Ratio: {textureScaleRatio:P2}"); } foreach (var tex in uniqueTextures) { string texPath = AssetDatabase.GetAssetPath(tex); if (string.IsNullOrEmpty(texPath)) continue; TextureImporter importer = AssetImporter.GetAtPath(texPath) as TextureImporter; if (importer != null) { int currentMaxDim = Mathf.Max(tex.width, tex.height); importer.mipmapEnabled = false; importer.textureCompression = TextureImporterCompression.CompressedLQ; if (textureScaleRatio < 1.0f) { int targetDim = (int)(currentMaxDim * textureScaleRatio); int powerOf2 = 32; while (powerOf2 * 2 <= targetDim && powerOf2 < 2048) { powerOf2 *= 2; } importer.maxTextureSize = powerOf2; } else { importer.maxTextureSize = Mathf.NextPowerOfTwo(currentMaxDim); } importer.SaveAndReimport(); Debug.Log($"✅ [TEXTURE] {tex.name} divalidasi pada resolusi: {importer.maxTextureSize}px"); } } // ==================================================================== // --- TAHAP PENJAGAAN MATERIAL BAWAAN --- // Kita biarkan material terhubung ke file .mat aslinya. Tidak ada kloning! // ==================================================================== Debug.Log("🎨 [MATERIAL] Material bawaan & Custom Shader dipertahankan sepenuhnya."); // ==================================================================== // --- TAHAP SMART MINIFIER POLIGON + EMBEDDING MESH --- // ==================================================================== var skinnedRenderers = instance.GetComponentsInChildren(true); var meshFilters = instance.GetComponentsInChildren(true); int totalOriginalTris = 0; // BYPASS DRM ZEPETO: Menghitung poligon dengan GetIndexCount foreach (var smr in skinnedRenderers) { if (smr.sharedMesh != null) { for (int i = 0; i < smr.sharedMesh.subMeshCount; i++) { totalOriginalTris += (int)(smr.sharedMesh.GetIndexCount(i) / 3); } } } foreach (var mf in meshFilters) { if (mf.sharedMesh != null) { for (int i = 0; i < mf.sharedMesh.subMeshCount; i++) { totalOriginalTris += (int)(mf.sharedMesh.GetIndexCount(i) / 3); } } } float quality = (totalOriginalTris > targetTris) ? (float)targetTris / (float)totalOriginalTris : 1.0f; Debug.Log($"📊 [MESH] Poligon Asli: {totalOriginalTris} tris | Target: {targetTris} | Rasio: {quality}"); foreach (var smr in skinnedRenderers) { if (smr.sharedMesh == null) continue; var meshSimplifier = new UnityMeshSimplifier.MeshSimplifier(); meshSimplifier.Initialize(smr.sharedMesh); meshSimplifier.SimplifyMesh(quality); Mesh optimizedMesh = meshSimplifier.ToMesh(); if (optimizedMesh != null) { optimizedMesh.name = smr.sharedMesh.name + "_embedded"; AssetDatabase.AddObjectToAsset(optimizedMesh, finalPrefabPath); smr.sharedMesh = optimizedMesh; } } foreach (var mf in meshFilters) { if (mf.sharedMesh == null) continue; var meshSimplifier = new UnityMeshSimplifier.MeshSimplifier(); meshSimplifier.Initialize(mf.sharedMesh); meshSimplifier.SimplifyMesh(quality); Mesh optimizedMesh = meshSimplifier.ToMesh(); if (optimizedMesh != null) { optimizedMesh.name = mf.sharedMesh.name + "_embedded"; AssetDatabase.AddObjectToAsset(optimizedMesh, finalPrefabPath); mf.sharedMesh = optimizedMesh; } } // ==================================================================== // --- FINALISASI & PACKING --- // ==================================================================== GameObject optimizedPrefab = PrefabUtility.SaveAsPrefabAsset(instance, finalPrefabPath); UnityEngine.Object.DestroyImmediate(instance); AssetDatabase.SaveAssets(); Debug.Log($"✅ [BUILDER] Prefab siap dengan mesh terpadatkan dan material bawaan utuh!"); Debug.Log("🚀 [PACKING] Menjalankan ZepetoAssetPackage.Pack..."); var result = new StringBuilder(); var statistics = ZepetoAssetBundleInfo.Create(optimizedPrefab); if (statistics != null) { result.AppendLine("[ZEPETO STUDIO ARCHIVE RESULT]"); result.AppendLine(JsonUtility.ToJson(statistics, true)); } var packResult = ZepetoAssetPackage.Pack(optimizedPrefab); result.Append(packResult); Debug.Log(result.ToString()); Debug.Log("✅ [BUILDER] Seluruh proses konversi sukses sempurna."); EditorApplication.UnlockReloadAssemblies(); EditorApplication.Exit(0); } catch (Exception e) { Debug.LogError($"🔥 [BUILDER] Error: {e.Message}"); Debug.LogError($"StackTrace: {e.StackTrace}"); EditorApplication.UnlockReloadAssemblies(); EditorApplication.Exit(1); } } }