using UnityEditor; using UnityEngine; using System.IO; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using CustomAssetBundleSystem; /// /// Unity Editor tool to build AssetBundles and generate catalog.json /// Menu: Tools > AssetBundles > Build for Android /// public class AssetBundleBuilder : EditorWindow { private static string outputPath = "ServerData/AssetBundles/Android"; private static BuildTarget buildTarget = BuildTarget.Android; [MenuItem("Tools/AssetBundles/Build for Android")] public static void BuildAssetBundles() { Debug.Log("=== Custom AssetBundle Builder ==="); // Create output directory string fullOutputPath = Path.Combine(Application.dataPath, "..", outputPath); if (!Directory.Exists(fullOutputPath)) { Directory.CreateDirectory(fullOutputPath); Debug.Log($"Created output directory: {fullOutputPath}"); } // Build options BuildAssetBundleOptions options = BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.StrictMode; // Build the bundles Debug.Log($"Building AssetBundles to: {fullOutputPath}"); Debug.Log($"Target platform: {buildTarget}"); AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles( fullOutputPath, options, buildTarget ); if (manifest == null) { Debug.LogError("✗ AssetBundle build failed!"); EditorUtility.DisplayDialog("Build Failed", "AssetBundle build failed! Check console for errors.", "OK"); return; } Debug.Log("✓ AssetBundles built successfully!"); // Generate the JSON catalog GenerateCatalog(fullOutputPath, manifest); Debug.Log("=== Build Complete ==="); EditorUtility.DisplayDialog("Build Complete", $"AssetBundles built successfully!\n\nOutput: {fullOutputPath}\n\nNext: Upload to VPS", "OK"); } /// /// Generate catalog.json from built AssetBundles /// private static void GenerateCatalog(string outputPath, AssetBundleManifest manifest) { Debug.Log("Generating catalog.json..."); var catalog = new RemoteAssetCatalog { version = Application.version, timestamp = System.DateTimeOffset.UtcNow.ToUnixTimeSeconds(), baseUrl = "https://assetbundle.uncaptured.co", bundles = new List() }; // Get all bundle names string[] allBundles = manifest.GetAllAssetBundles(); foreach (string bundleName in allBundles) { string bundlePath = Path.Combine(outputPath, bundleName); if (!File.Exists(bundlePath)) { Debug.LogWarning($"Bundle file not found: {bundlePath}"); continue; } // Get file info FileInfo fileInfo = new FileInfo(bundlePath); // Calculate hash string hash = CalculateMD5(bundlePath); // Get scenes in this bundle var scenePaths = AssetDatabase.GetAssetPathsFromAssetBundle(bundleName); var sceneNames = new List(); foreach (var scenePath in scenePaths) { if (scenePath.EndsWith(".unity")) { string sceneName = Path.GetFileNameWithoutExtension(scenePath); sceneNames.Add(sceneName); } } // Get dependencies var dependencies = new List(); string[] bundleDeps = manifest.GetAllDependencies(bundleName); dependencies.AddRange(bundleDeps); // Create bundle entry var entry = new RemoteAssetCatalog.BundleEntry { bundleName = Path.GetFileNameWithoutExtension(bundleName), fileName = bundleName, hash = hash, size = fileInfo.Length, scenes = sceneNames, dependencies = dependencies }; catalog.bundles.Add(entry); Debug.Log($" ✓ Added bundle: {bundleName}"); Debug.Log($" - Size: {fileInfo.Length / 1024f / 1024f:F2} MB"); Debug.Log($" - Hash: {hash}"); Debug.Log($" - Scenes: {sceneNames.Count}"); Debug.Log($" - Dependencies: {dependencies.Count}"); } // Save catalog as JSON string catalogPath = Path.Combine(outputPath, "catalog.json"); string json = JsonUtility.ToJson(catalog, true); File.WriteAllText(catalogPath, json); Debug.Log($"✓ Catalog generated: {catalogPath}"); Debug.Log($" Total bundles: {catalog.bundles.Count}"); Debug.Log($" Catalog size: {json.Length / 1024f:F2} KB"); // Also save a pretty-printed version for debugging string prettyPath = Path.Combine(outputPath, "catalog_readable.json"); File.WriteAllText(prettyPath, json); Debug.Log($"✓ Readable catalog: {prettyPath}"); } /// /// Calculate MD5 hash of a file /// private static string CalculateMD5(string filePath) { using (var md5 = MD5.Create()) using (var stream = File.OpenRead(filePath)) { byte[] hash = md5.ComputeHash(stream); StringBuilder sb = new StringBuilder(); foreach (byte b in hash) { sb.Append(b.ToString("x2")); } return sb.ToString(); } } [MenuItem("Tools/AssetBundles/Open Output Folder")] public static void OpenOutputFolder() { string fullPath = Path.Combine(Application.dataPath, "..", outputPath); if (Directory.Exists(fullPath)) { EditorUtility.RevealInFinder(fullPath); } else { Debug.LogWarning($"Output folder doesn't exist: {fullPath}"); } } [MenuItem("Tools/AssetBundles/Clear Cache")] public static void ClearCache() { if (EditorUtility.DisplayDialog("Clear AssetBundle Cache?", "This will delete all cached AssetBundles. Continue?", "Yes", "Cancel")) { Caching.ClearCache(); Debug.Log("✓ AssetBundle cache cleared"); } } [MenuItem("Tools/AssetBundles/Settings")] public static void ShowSettings() { var window = GetWindow("AssetBundle Builder"); window.Show(); } // Editor Window GUI void OnGUI() { GUILayout.Label("Custom AssetBundle Builder", EditorStyles.boldLabel); GUILayout.Space(10); EditorGUILayout.HelpBox( "This tool builds AssetBundles and generates a JSON catalog for remote loading.\n\n" + "1. Mark your scenes/assets with AssetBundle names\n" + "2. Click 'Build for Android'\n" + "3. Upload ServerData/AssetBundles/Android/ to your VPS", MessageType.Info); GUILayout.Space(10); // Output path GUILayout.Label("Output Path:", EditorStyles.boldLabel); outputPath = EditorGUILayout.TextField(outputPath); GUILayout.Space(5); // Build target GUILayout.Label("Build Target:", EditorStyles.boldLabel); buildTarget = (BuildTarget)EditorGUILayout.EnumPopup(buildTarget); GUILayout.Space(10); // Build button if (GUILayout.Button("Build AssetBundles", GUILayout.Height(40))) { BuildAssetBundles(); } GUILayout.Space(5); // Utility buttons using (new EditorGUILayout.HorizontalScope()) { if (GUILayout.Button("Open Output Folder")) { OpenOutputFolder(); } if (GUILayout.Button("Clear Cache")) { ClearCache(); } } GUILayout.Space(20); // Info section GUILayout.Label("Current Settings:", EditorStyles.boldLabel); EditorGUILayout.LabelField("Unity Version:", Application.unityVersion); EditorGUILayout.LabelField("App Version:", Application.version); EditorGUILayout.LabelField("Company:", Application.companyName); GUILayout.Space(10); // Quick guide if (GUILayout.Button("Show Quick Guide")) { ShowQuickGuide(); } } private static void ShowQuickGuide() { EditorUtility.DisplayDialog("AssetBundle Quick Guide", "STEP 1: Mark Assets for Bundling\n" + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + "• Select your Game scene in Project window\n" + "• In Inspector, find 'AssetBundle' dropdown (bottom)\n" + "• Select 'New...' and name it 'gamescene'\n\n" + "STEP 2: Build AssetBundles\n" + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + "• Tools → AssetBundles → Build for Android\n" + "• Wait for build to complete\n" + "• Files created in ServerData/AssetBundles/Android/\n\n" + "STEP 3: Upload to VPS\n" + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + "• scp -r ServerData/AssetBundles/Android/* user@server:/var/www/AssetBundles/Android/\n\n" + "STEP 4: Update Your Code\n" + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + "• Replace Addressables calls with CustomAssetBundleSceneLoader\n" + "• Build APK and test!", "Got it!"); } }