using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using UnityEditor; using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Build; using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEditor.Android; using UnityEditor.Build; using UnityEditor.Build.Pipeline; using UnityEngine; using UnityEngine.AddressableAssets; using Debug = UnityEngine.Debug; /// /// 所有资源全用远程构建远程加载非静态的方式来做。 /// 通过DefaultBuild来做首次构建。 /// 关于crc建议:静态资源不要勾选,非静态资源勾不勾选都可以 /// 安卓会直接缓存.待测试. /// public class AppBuilder { [MenuItem("Build/资源界面", priority = 2050)] internal static void Init() { EditorApplication.ExecuteMenuItem("Window/Asset Management/Addressables/Groups"); } [MenuItem("Build/更新资源", priority = 2049)] public static void AutoGroup() { //是否启用简单命名方式 if (!Selection.activeObject) return; var labels = AssetDatabase.GetLabels(Selection.activeObject); if (labels == null || labels.Length == 0) return; if (!Array.Exists(labels, (x) => x == "AssetGroup")) { Log("缺少 AssetGroup 标记"); return; } var selectPath = AssetDatabase.GetAssetPath(Selection.activeObject); if (!AssetDatabase.IsValidFolder(selectPath)) { Log("AssetGroup 只能是文件夹"); return; } var settings = AddressableAssetSettingsDefaultObject.Settings; var groupName = Path.GetFileName(selectPath); var group = settings.FindGroup(groupName); if (!group) { Log("AssetGroup 不存在"); return; } var fileSystemEntries = Directory.GetFileSystemEntries(selectPath); foreach (var fileSystemEntry in fileSystemEntries) { if (!".meta".Equals(Path.GetExtension(fileSystemEntry))) { var guid = AssetDatabase.AssetPathToGUID(fileSystemEntry); var entry = settings.CreateOrMoveEntry(guid, group); if (entry != null) entry.SetAddress(Path.GetFileNameWithoutExtension(fileSystemEntry), false); } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Log($"更新资源组 {groupName}"); } //创建组 private static AddressableAssetGroup CreateOrGetNonStaticGroup(AddressableAssetSettings settings, string groupName) { var group = settings.FindGroup(groupName); if (group == null) group = settings.CreateGroup(groupName, false, false, false, null, typeof(BundledAssetGroupSchema), typeof(ContentUpdateGroupSchema)); group.GetSchema().StaticContent = false; BundledAssetGroupSchema groupSchema = group.GetSchema(); //groupSchema.UseAssetBundleCrc = false; //groupSchema.BundleNaming = BundledAssetGroupSchema.BundleNamingStyle.OnlyHash; groupSchema.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackSeparately; groupSchema.BuildPath.SetVariableByName(settings, AddressableAssetSettings.kRemoteBuildPath); groupSchema.BuildPath.SetVariableByName(settings, AddressableAssetSettings.kRemoteLoadPath); return group; } //打包 [MenuItem("Build/资源打包", false, 1)] public static void BuildContent() { AddressableAssetSettings.BuildPlayerContent(); } //更新 [MenuItem("Build/资源更新包", false, 2)] public static void CheckForUpdateContent() { //与上次打包做资源对比 var buildPath = ContentUpdateScript.GetContentStateDataPath(false); var Settings = AddressableAssetSettingsDefaultObject.Settings; var entries = ContentUpdateScript.GatherModifiedEntries(Settings, buildPath); if (entries.Count == 0) { Debug.Log("没有资源变更"); return; } StringBuilder sb = new StringBuilder(); sb.AppendLine("Need Update Assets:"); foreach (var entry in entries) { sb.AppendLine(entry.address); } Debug.Log(sb.ToString()); //将被修改过的资源单独分组---可以自定义组名 var groupName = string.Format("UpdateGroup_{0}", DateTime.Now.ToString("yyyyMMddHHmmss")); ContentUpdateScript.CreateContentUpdateGroup(Settings, entries, groupName); } public static void ClearLabel() { AddressableAssetSettings assetSettings = AddressableAssetSettingsDefaultObject.GetSettings(false); var labelList = assetSettings.GetLabels(); foreach (var item in labelList) { assetSettings.RemoveLabel(item); } } public static void ClearAllLabel() { //需要添加标签 AddressableAssetSettings assetSettings = AddressableAssetSettingsDefaultObject.GetSettings(false); for (int i = assetSettings.groups.Count - 1; i >= 0; --i) { AddressableAssetGroup assetGroup = assetSettings.groups[i]; foreach (var item in assetGroup.entries) { // item.SetLabel("已定义的标签名",true); } } } [MenuItem("Build/打开远程构建目录", false, 5)] public static void BuildUpdate() { var m_Settings = AddressableAssetSettingsDefaultObject.Settings; string path = (Application.dataPath.Substring(0, Application.dataPath.LastIndexOf('/')) + "/" + m_Settings.RemoteCatalogBuildPath.GetValue(m_Settings)); MyEditorTools.ShowExplorer(path.Substring(0, path.LastIndexOf('/'))); } [MenuItem("Build/打开本地构建目录", false, 6)] public static void OpenLocalBuild() { MyEditorTools.ShowExplorer(Addressables.BuildPath); } [MenuItem("Build/更新小游戏资源", false, 6)] public static void UpdateMiniGame() { var mingamePath = "../../MiniGame/webgl/StreamingAssets/aa"; if (Directory.Exists(mingamePath)) { Directory.Delete(mingamePath, true); } var buildPath = Addressables.BuildPath; if (Directory.Exists(buildPath)) { FileUtil.CopyFileOrDirectory(buildPath, mingamePath); Debug.Log("更新资源成功"); } } /// /// /// public static void SetPlatform_Wechat() { ClearPlatform_OneSdk(); ClearPlatform_Ohayoo(); if (!Directory.Exists("Assets/WX-WASM-SDK")) { FileUtil.CopyFileOrDirectory("../sdk/WX-WASM-SDK//WX-WASM-SDK", "Assets//WX-WASM-SDK"); FileUtil.CopyFileOrDirectory("../sdk/WX-WASM-SDK/WebGLTemplates", "Assets/WebGLTemplates"); } var defines = "SDK_WECHAT_WASM"; PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.WebGL, defines); AssetDatabase.Refresh(); if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.WebGL) { EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.WebGL, BuildTarget.WebGL); return; } Log("切换微信平台"); } public static void ClearPlatform_Wechat() { if (Directory.Exists("Assets/WX-WASM-SDK")) { Directory.Delete("Assets/WX-WASM-SDK", true); FileUtil.DeleteFileOrDirectory("Assets/WX-WASM-SDK.meta"); } if (Directory.Exists("Assets/WebGLTemplates")) { Directory.Delete("Assets/WebGLTemplates", true); FileUtil.DeleteFileOrDirectory("Assets/WebGLTemplates.meta"); } AssetDatabase.Refresh(); } /// /// /// public static void SetPlatform_OneSdk() { ClearPlatform_Wechat(); ClearPlatform_Ohayoo(); if (!Directory.Exists("Assets/GameSDK")) { FileUtil.CopyFileOrDirectory("../sdk/GameSDK/GameSDK", "Assets/GameSDK"); FileUtil.CopyFileOrDirectory("../sdk/GameSDK/Plugins", "Assets/Plugins"); } var defines = "SDK_ONESDK"; PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defines); AssetDatabase.Refresh(); Log("切换 SDK_ONESDK"); } public static void ClearPlatform_OneSdk() { if (Directory.Exists("Assets/Plugins")) { Directory.Delete("Assets/Plugins", true); FileUtil.DeleteFileOrDirectory("Assets/Plugins.meta"); } if (Directory.Exists("Assets/GameSDK")) { Directory.Delete("Assets/GameSDK", true); FileUtil.DeleteFileOrDirectory("Assets/GameSDK.meta"); AssetDatabase.Refresh(); } } /// /// /// public static void SetPlatform_Ohayoo() { ClearPlatform_Wechat(); ClearPlatform_OneSdk(); if (!Directory.Exists("Assets/LightGameSDK")) { FileUtil.CopyFileOrDirectory("../sdk/LightGameSDK/LightGameSDK", "Assets/LightGameSDK"); FileUtil.CopyFileOrDirectory("../sdk/LightGameSDK/Plugins", "Assets/Plugins"); } else { } var defines = "SDK_OHAYOO"; PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defines); AssetDatabase.Refresh(); Log("切换 SDK_OHAYOO"); } public static void ClearPlatform_Ohayoo() { if (Directory.Exists("Assets/Plugins")) { Directory.Delete("Assets/Plugins", true); FileUtil.DeleteFileOrDirectory("Assets/Plugins.meta"); } if (Directory.Exists("Assets/LightGameSDK")) { Directory.Delete("Assets/LightGameSDK", true); FileUtil.DeleteFileOrDirectory("Assets/LightGameSDK.meta"); AssetDatabase.Refresh(); } } /// /// /// /// /// /// public static void SetAndroid(string appId, string appName, string channel) { #if SDK_OHAYOO ByteDance.Union.LightGameParamsManager.FieldValueChange("Android_Channel", channel); ByteDance.Union.LightGameParamsManager.FieldValueChange("Android_AppName", appName); #endif PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, appId); //var defines = "CHANNEL_OHAYOO"; //PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defines); AssetDatabase.Refresh(); Log($"{channel} 设置成功"); } /// /// /// public static void SetAndroidOneSdk() { PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, "com.kunpo.litaibai.android.ohayoo.onesdk"); //var defines = "CHANNEL_ONESDK"; //PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defines); AssetDatabase.Refresh(); Log($"{"onesdk"} 设置成功"); } /// /// /// public static void SetAndroidDebug() { #if SDK_OHAYOO ByteDance.Union.LightGameParamsManager.FieldValueChange("Android_Channel", "jrtt"); ByteDance.Union.LightGameParamsManager.FieldValueChange("Android_AppName", "少侠李太白_android"); #endif PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, "com.kunpo.litaibai.android.ohayoo"); var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android); if (!defines.StartsWith("DEBUG_MY;")) { defines = "DEBUG_MY;" + defines; PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defines); } AssetDatabase.Refresh(); Log($"{"debug"} 设置成功"); } public static void SetWebGLWechat(string appId, string appName, string channel) { PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.WebGL, appId); //var defines = "CHANNEL_OHAYOO"; //PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defines); AssetDatabase.Refresh(); Log($"{channel} 设置成功"); } public static void BuildApp(string appPath, BuildTargetGroup targetGroup, BuildTarget buildTarget, bool buildContent) { if (EditorUserBuildSettings.activeBuildTarget != buildTarget) { EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, buildTarget); return; } if (buildContent) BuildContent(); var report = BuildPipeline.BuildPlayer(EditorBuildSettings.scenes, appPath, buildTarget, 0); var summary = report.summary; if (summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded) { Log(summary.outputPath); } } public static void Encrypt_Apk(string oldApk, string newApk) { var argList = new System.Text.StringBuilder(); argList.Append(@"python.exe"); argList.Append(@" C:\Projects\arpg\wkpacker_10010\client.py"); argList.Append($" -u {"be1aa5afa0197474"}"); argList.Append($" -i {oldApk}"); argList.Append($" -o {newApk}"); argList.Append(" --sg -z"); ProcessHelper.Run("powershell.exe", argList.ToString(), ".", false); } public static void Sign_Apk(string signApk) { var argList = new System.Text.StringBuilder(); argList.Append("sign"); argList.Append(@" --ks C:\Projects\arpg\Android\rpg.keystore"); argList.Append(" --ks-key-alias com.kunpo.rpg"); argList.Append(" --ks-pass pass:blueshow88 "); argList.Append(signApk); var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); //apksigner sign --ks 密钥库名--ks - key - alias 密钥别名 xxx.apk ProcessHelper.Run(path + @"\Android\Sdk\build-tools\31.0.0\apksigner.bat", argList.ToString(), ".", true); } public static string GetApkPath() { if (Directory.Exists(@"D:\shaoxiataibai\")) return @"D:\shaoxiataibai\"; return @"C:\Projects\arpg\Android\"; } static void Log(string text) { Debug.Log("[Build]" + text); } } class MyCustomBuildProcessor : IPostGenerateGradleAndroidProject { public int callbackOrder { get { return 0; } } public void OnPostGenerateGradleAndroidProject(string path) { var manifestPath = Path.Combine(path, @"src\main\AndroidManifest.xml"); var xmlText = File.ReadAllText(manifestPath); if (xmlText.Contains("singleTask")) { xmlText = xmlText.Replace("singleTask", "singleTop"); File.WriteAllText(manifestPath, xmlText); } Debug.Log("OnPostGenerateGradleAndroidProject at path " + path); } } public static class ProcessHelper { public static Process Run(string exe, string arguments, string workingDirectory = "", bool waitExit = false) { try { //bool redirectStandardOutput = true; //bool redirectStandardError = true; //bool useShellExecute = false; //if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) //{ // redirectStandardOutput = false; // redirectStandardError = false; // useShellExecute = true; //} ProcessStartInfo info = new ProcessStartInfo { FileName = exe, Arguments = arguments, CreateNoWindow = false, UseShellExecute = true, WorkingDirectory = workingDirectory, RedirectStandardOutput = false, RedirectStandardError = false, }; Process process = Process.Start(info); if (waitExit) { process.WaitForExit(); if (process.ExitCode != 0) { throw new Exception(process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd() + "\n" + $"请在terminal中执行,目录{workingDirectory}, 命令{exe} {arguments}"); } else { Debug.Log("执行成功"); } } return process; } catch (Exception e) { throw new Exception($"请在terminal中执行,目录{workingDirectory}, 命令{exe} {arguments}", e); } } } /// /// /// public class BuildWindow : EditorWindow { [System.Serializable] class BuildInfo { public List buildList = new List(); public int buildStatus; public int buildStep; public bool buildRes; } [MenuItem("Build/Open Window")] static void Init() { // Get existing open window or if none, make a new one: BuildWindow window = GetWindow("打包"); window.Show(); } readonly BuildInfo _buildInfo = new BuildInfo(); void ReadBuildInfo() { var buildInfoText = EditorPrefs.GetString("build_info"); if (string.IsNullOrEmpty(buildInfoText)) EditorJsonUtility.FromJsonOverwrite(buildInfoText, _buildInfo); } void SaveBuildInfo() { EditorPrefs.SetString("build_info", EditorJsonUtility.ToJson(_buildInfo)); } void BuildUI(string channel) { EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); bool myBool = false; myBool = EditorGUILayout.Toggle($"Android_{channel}", myBool, EditorStyles.boldLabel); if (GUILayout.Button("打包")) { BuildChannel(channel); } if (GUILayout.Button("加密")) { EncryptApk(channel); } if (GUILayout.Button("签名")) { SignApk(channel); } EditorGUILayout.EndHorizontal(); } /// /// /// /// void SetParams(string channel) { switch (channel) { case "jrtt": case "tap": case "hykb": case "wdjbb": AppBuilder.SetAndroid("com.kunpo.litaibai.android.ohayoo", "少侠李太白_android", channel); break; case "vapp": AppBuilder.SetAndroid("com.kunpo.litaibai.android.ohayoo.vapp", "少侠李太白_vapp", channel); break; case "ssjj": AppBuilder.SetAndroid("com.kunpo.litaibai.android.ssjj", "少侠李太白_4399", channel); break; case "qqers": AppBuilder.SetAndroid("com.kunpo.litaibai.android.qqers", "少侠李太白_7723", channel); break; case "jiuyou": AppBuilder.SetAndroid("com.kunpo.litaibai.android.uc", "少侠太白_jiuyou", channel); break; case "cczs": AppBuilder.SetAndroid("com.kunpo.litaibai.android.cczs", "少侠李太白_cczs", channel); break; case "debug": AppBuilder.SetAndroidDebug(); break; case "onesdk": AppBuilder.SetAndroidOneSdk(); break; default: Debug.Log("暂不支持渠道"); break; } } /// /// /// /// void BuildApk(string channel) { var apkName = $"shaoxiataibai_{PlayerSettings.bundleVersion.Replace('.', '_')}_{channel}"; var apkPath = AppBuilder.GetApkPath(); AppBuilder.BuildApp(apkPath + apkName + ".apk", BuildTargetGroup.Android, BuildTarget.Android, false); } /// /// /// void BuildRes() { AppBuilder.BuildContent(); } /// /// /// /// void EncryptApk(string channel) { if (channel == "debug") return; var apkName = $"shaoxiataibai_{PlayerSettings.bundleVersion.Replace('.', '_')}_{channel}"; var apkPath = AppBuilder.GetApkPath(); AppBuilder.Encrypt_Apk(apkPath + apkName + ".apk", apkPath + apkName + "_e.apk"); } /// /// /// /// void SignApk(string channel) { if (channel == "debug") return; var apkName = $"shaoxiataibai_{PlayerSettings.bundleVersion.Replace('.', '_')}_{channel}"; var apkPath = AppBuilder.GetApkPath(); AppBuilder.Sign_Apk(apkPath + apkName + "_e.apk"); } string _signChannel; void EncryptAndSignApk(string channel) { EncryptApk(channel); if (!string.IsNullOrEmpty(_signChannel)) { SignApk(_signChannel); } _signChannel = channel; } /// /// /// /// void SetSdk(string channel) { if ("onesdk".Equals(channel)) { AppBuilder.SetPlatform_OneSdk(); } else { AppBuilder.SetPlatform_Ohayoo(); } } void BuildChannel(string channel) { if (_buildInfo.buildList.Count == 0) { Log("开始打包 {channel}"); _buildInfo.buildList.Add(channel); _buildInfo.buildStatus = 0; _buildInfo.buildStep = 0; _buildInfo.buildRes = true; SaveBuildInfo(); } } void BuildAllChannel() { if (_buildInfo.buildList.Count == 0) { Log("开始一键打包"); _buildInfo.buildList.Add("jrtt"); _buildInfo.buildList.Add("tap"); _buildInfo.buildList.Add("hykb"); _buildInfo.buildList.Add("wdjbb"); _buildInfo.buildList.Add("ssjj"); _buildInfo.buildList.Add("qqers"); //_buildInfo.buildList.Add("jiuyou"); //_buildInfo.buildList.Add("cczs"); _buildInfo.buildList.Add("vapp"); _buildInfo.buildList.Add("debug"); _buildInfo.buildStatus = 0; _buildInfo.buildStep = 0; _buildInfo.buildRes = true; SaveBuildInfo(); } } void ClearAllBuild() { _buildInfo.buildList.Clear(); _buildInfo.buildStatus = 0; _buildInfo.buildStep = 0; SaveBuildInfo(); } void UpdateBuild() { if (_buildInfo.buildList.Count > 0) { var channel = _buildInfo.buildList[0]; switch (_buildInfo.buildStatus) { case 0: if (_buildInfo.buildRes) { Log("build res"); _buildInfo.buildRes = false; SaveBuildInfo(); BuildRes(); } else { Log("start build channel"); _buildInfo.buildStatus = 1; _buildInfo.buildStep = 1; SaveBuildInfo(); } break; case 1: { switch (_buildInfo.buildStep) { case 1: Log($"set build params {channel}"); _buildInfo.buildStep = 2; SaveBuildInfo(); SetParams(channel); break; case 2: Log($"build channel package {channel}"); _buildInfo.buildStep = 3; SaveBuildInfo(); BuildApk(channel); break; case 3: Log($"finish build channel {channel}"); _buildInfo.buildStatus = 2; SaveBuildInfo(); break; } } break; case 2: { Log($"completed channel {channel}"); _buildInfo.buildList.RemoveAt(0); _buildInfo.buildStatus = 0; SaveBuildInfo(); // EncryptAndSignApk(channel); } break; } } } void Log(string text) { Debug.Log("[Build]" + text); } private void Awake() { ReadBuildInfo(); } double _lastUpdateTime; void Update() { if (EditorApplication.timeSinceStartup - _lastUpdateTime > 1f) { _lastUpdateTime = EditorApplication.timeSinceStartup; UpdateBuild(); } } void OnGUI() { if (_buildInfo.buildList.Count > 0) { this.titleContent = new GUIContent(_buildInfo.buildList[0] + " 打包中....."); } EditorGUILayout.BeginVertical(); EditorGUILayout.Space(); BuildUI("jrtt"); EditorGUILayout.Space(); BuildUI("tap"); EditorGUILayout.Space(); BuildUI("hykb"); EditorGUILayout.Space(); BuildUI("wdjbb"); EditorGUILayout.Space(); EditorGUILayout.Separator(); EditorGUILayout.Space(); BuildUI("vapp"); EditorGUILayout.Space(); BuildUI("ssjj"); EditorGUILayout.Space(); BuildUI("qqers"); EditorGUILayout.Space(); //BuildUI("jiuyou"); EditorGUILayout.Space(); //BuildUI("cczs"); EditorGUILayout.Separator(); EditorGUILayout.Separator(); EditorGUILayout.Separator(); //BuildUI("onesdk"); EditorGUILayout.Separator(); EditorGUILayout.Separator(); EditorGUILayout.Separator(); EditorGUILayout.Space(); BuildUI("debug"); EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(EditorStyles.textArea); if (GUILayout.Button("设置ohayoo平台")) { AppBuilder.SetPlatform_Ohayoo(); } if (GUILayout.Button("一键打包")) { BuildAllChannel(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); EditorGUILayout.Space(); EditorGUILayout.Space(); EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(EditorStyles.textArea); if (GUILayout.Button("设置onesdk平台")) { AppBuilder.SetPlatform_OneSdk(); } if (GUILayout.Button("一键打包")) { BuildAllChannel(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); EditorGUILayout.Space(); EditorGUILayout.Space(); EditorGUILayout.Space(); EditorGUILayout.BeginHorizontal(EditorStyles.textArea); if (GUILayout.Button("设置微信平台")) { AppBuilder.SetPlatform_Wechat(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } }