using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using UnityEditor; using UnityEngine; public class PackageExporter { /// /// 导出 UnityPackage(支持忽略特定文件) /// /// 需要导出的资源路径数组(文件夹或文件) /// 导出文件路径(含.unitypackage扩展名) /// 需要忽略的文件/文件夹模式数组 /// 是否包含依赖资源 /// 是否递归导出文件夹内的所有内容 /// 导出是否成功 private static bool ExportUnityPackage( string[] assetPaths, string outputPath, string[] ignorePatterns = null, bool includeDependencies = true, bool recurseFolders = true ) { // 1. 验证资源路径 if (!ValidateAssetPaths(assetPaths)) { Debug.LogError("导出失败:无效的资源路径"); return false; } // 2. 验证输出路径 if (!ValidateAndPrepareOutputPath(outputPath)) { Debug.LogError("导出失败:无效的输出路径"); return false; } try { // 3. 处理需要导出的路径(过滤掉需要忽略的文件) var temp = ProcessAssetPaths(assetPaths, ignorePatterns, recurseFolders); var filteredPaths = temp.Where(File.Exists).ToArray(); if (filteredPaths.Length == 0) { Debug.LogError("导出失败:所有路径都被过滤掉了"); return false; } // 4. 构建导出选项 ExportPackageOptions options = ExportPackageOptions.Default; // 如果需要递归文件夹,添加 Recurse 选项 if (recurseFolders) { options |= ExportPackageOptions.Recurse; } // 如果需要包含依赖资源,添加 IncludeDependencies 选项 if (includeDependencies) { options |= ExportPackageOptions.IncludeDependencies; } // 5. 执行导出 AssetDatabase.ExportPackage(filteredPaths, outputPath, options); // 6. 验证结果 if (File.Exists(outputPath)) { Debug.Log($"导出成功:{outputPath}"); return true; } Debug.LogError("导出失败:文件未生成"); return false; } catch (Exception ex) { Debug.LogError($"导出异常:{ex.Message}"); return false; } } /// /// 处理资源路径,过滤掉需要忽略的文件 /// private static string[] ProcessAssetPaths(string[] assetPaths, string[] ignorePatterns, bool recurseFolders) { if (ignorePatterns == null || ignorePatterns.Length == 0) { return assetPaths; // 没有需要忽略的模式,直接返回原路径 } // 收集所有需要导出的文件路径 var allFiles = new System.Collections.Generic.List(); foreach (var path in assetPaths) { if (AssetDatabase.IsValidFolder(path)) { // 如果是文件夹,获取所有文件 var filesInFolder = AssetDatabase.FindAssets("", new[] { path }) .Select(AssetDatabase.GUIDToAssetPath) .ToArray(); // 根据递归选项和忽略模式过滤文件 allFiles.AddRange(filesInFolder.Where(file => !ShouldIgnore(file, ignorePatterns) && (recurseFolders || Path.GetDirectoryName(file) == path))); var googleMobileAdsAndroidLib = Directory.GetDirectories(path, "GoogleMobileAdsPlugin", SearchOption.AllDirectories) .Where(p => p.Replace("\\", "/").StartsWith("Assets/")) .Select(p => p.Replace("\\", "/")) .ToArray(); Debug.Log("yangwu firebase:"+googleMobileAdsAndroidLib); allFiles.AddRange(googleMobileAdsAndroidLib); } else if (File.Exists(path)) { // 如果是文件,检查是否需要忽略 if (!ShouldIgnore(path, ignorePatterns)) { allFiles.Add(path); } } var androidLibs = Directory.GetDirectories(path, "GoogleMobileAdsPlugin.androidlib", SearchOption.AllDirectories) .Where(p => p.Replace("\\", "/").StartsWith("Assets/")) .Select(p => p.Replace("\\", "/")) .ToArray(); allFiles.AddRange(androidLibs); } return allFiles.Distinct().ToArray(); } /// /// 检查文件是否应该被忽略 /// private static bool ShouldIgnore(string filePath, string[] ignorePatterns) { if (!ignorePatterns.Any(pattern => filePath.Contains(pattern) || Path.GetFileName(filePath).Contains(pattern) || (pattern.Contains("*") && MatchesWildcard(filePath, pattern)))) return false; Debug.Log("ShouldIgnore true: " + filePath); return true; } /// /// 检查文件路径是否匹配通配符模式(使用正则表达式实现) /// private static bool MatchesWildcard(string filePath, string pattern) { try { // 将通配符模式转换为正则表达式 var regexPattern = "^" + Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$"; var regex = new Regex(regexPattern, RegexOptions.IgnoreCase); // 检查文件名和完整路径是否匹配 return regex.IsMatch(Path.GetFileName(filePath)) || regex.IsMatch(filePath); } catch { return false; } } // 以下为辅助方法 private static bool ValidateAssetPaths(string[] assetPaths) { if (assetPaths == null || assetPaths.Length == 0) { Debug.LogWarning("未指定任何资源路径"); return false; } foreach (var path in assetPaths) { if (string.IsNullOrWhiteSpace(path) || !path.StartsWith("Assets/")) { Debug.LogWarning($"无效路径格式:{path}(必须以 Assets/ 开头)"); return false; } if (AssetDatabase.IsValidFolder(path) || File.Exists(path)) continue; Debug.LogWarning($"路径不存在:{path}"); return false; } return true; } private static bool ValidateAndPrepareOutputPath(string outputPath) { if (string.IsNullOrWhiteSpace(outputPath) || !outputPath.EndsWith(".unitypackage")) { Debug.LogWarning("输出路径必须包含 .unitypackage 扩展名"); return false; } var directory = Path.GetDirectoryName(outputPath); if (Directory.Exists(directory)) return true; try { if (directory == null) { return false; } Directory.CreateDirectory(directory); } catch (Exception ex) { Debug.LogError($"无法创建目录:{ex.Message}"); return false; } return true; } // ------------------------------ // 快捷导出示例(可直接在菜单调用) // ------------------------------ [MenuItem("Tools/Export UnityPackage")] public static void ExportExamplePackage() { var pathsToExport = new[] { "Assets/Adjust", "Assets/BigoAds", "Assets/BigoSDK", "Assets/Editor", "Assets/Editor Default Resources", "Assets/ExternalDependencyManager", "Assets/EFSDK", "Assets/Firebase", "Assets/GeneratedLocalRepo", "Assets/GoogleMobileAds", "Assets/KwaiAds", "Assets/MaxSdk", "Assets/Plugins", "Assets/Script", "Assets/ThinkingAnalytics", "Assets/ThinkupTpnPlugin", "Assets/UnityPackages" }; // 定义需要忽略的文件/文件夹模式 var ignorePatterns = new[] { "Assets/Plugins/Android/AndroidManifest.xml", "Assets/Plugins/Android/baseProjectTemplate.gradle", "Assets/Plugins/Android/gradleTemplate.properties", "Assets/Plugins/Android/LauncherManifest.xml", "Assets/Plugins/Android/launcherTemplate.gradle", "Assets/Plugins/Android/mainTemplate.gradle", "Assets/Plugins/Android/MessagingUnityPlayerActivity.java", "Assets/Plugins/Android/settingsTemplate.gradle", }; // 获取项目根目录(Assets文件夹的上级目录) var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); var outputPath = Path.Combine(projectRoot, $"RushSDK_{RushSDKManager.GetSDKVersion()}_{DateTime.Now:yyyyMMdd_HHmmss}.unitypackage"); // 执行导出(不包含依赖,使用我们定义的忽略模式) ExportUnityPackage(pathsToExport, outputPath, ignorePatterns, includeDependencies: false); } }