using System.Collections;
|
using System.Collections.Generic;
|
using UnityEngine;
|
using System;
|
using UnityEditor;
|
using System.IO;
|
using System.Text.RegularExpressions;
|
using System.Text;
|
using Cysharp.Threading.Tasks;
|
using YooAsset.Editor;
|
|
//using Beebyte.Obfuscator;
|
|
public class ClientPackage
|
{
|
public static readonly string versionsFilePath = Application.dataPath + "/Editor/VersionConfigs/Versions.txt";
|
public static readonly string[] baseLevels = new string[] { "Assets/Resources/Scenes/Launch.unity" };
|
|
public static string auditOutTime = string.Empty;
|
|
public static string SDK_PLUGIN_PROJECT
|
{
|
get { return LocalSave.GetString("SDK_PROJECT_PATH"); }
|
set { LocalSave.SetString("SDK_PROJECT_PATH", value); }
|
}
|
|
public static bool obfuscatorEnabled
|
{
|
get { return LocalSave.GetBool("obfuscatorEnabled", false); }
|
set { LocalSave.SetBool("obfuscatorEnabled", value); }
|
}
|
public static bool includeConfig
|
{
|
get { return LocalSave.GetBool("client_pg_includeConfig", false); }
|
set { LocalSave.SetBool("client_pg_includeConfig", value); }
|
}
|
|
public static bool includeUI
|
{
|
get { return LocalSave.GetBool("client_pg_includeUI", false); }
|
set { LocalSave.SetBool("client_pg_includeUI", value); }
|
}
|
|
public static int AssetPrior
|
{
|
get { return LocalSave.GetInt("HalfAssetPrior", 1); }
|
set { LocalSave.SetInt("HalfAssetPrior", value); }
|
}
|
|
static string THE_2TH_WORLD_SDK_PATH = Application.dataPath + "/Plugins/Android/bin";
|
static string MANIFEST_PATH = Application.dataPath + "/Plugins/Android/AndroidManifest.xml";
|
static string iOS_PLUGIN_PATH = Application.dataPath + "/Plugins/iOS/Channel";
|
|
static string File_baseProjectTemplate = Application.dataPath + "/Plugins/Android/baseProjectTemplate.gradle";
|
static string File_google_services = Application.dataPath + "/Plugins/Android/google-services.json";
|
static string File_gradleTemplate = Application.dataPath + "/Plugins/Android/gradleTemplate.properties";
|
static string File_LauncherManifest = Application.dataPath + "/Plugins/Android/LauncherManifest.xml";
|
static string File_launcherTemplate = Application.dataPath + "/Plugins/Android/launcherTemplate.gradle";
|
static string File_mainTemplate = Application.dataPath + "/Plugins/Android/mainTemplate.gradle";
|
static string File_deps = Application.dataPath + "/Plugins/Android/deps.gradle";
|
static string File_settingGradle = Application.dataPath + "/Plugins/Android/settingsTemplate.gradle";
|
static string File_huawei_services = Application.dataPath + "/Plugins/Android/agconnect-services.json";
|
|
/// <summary>
|
/// 打包
|
/// </summary>
|
/// <param name="_sdkPath">整合sdk路径</param>
|
/// <param name="_assetBundlePath">_assetBundle的资源路径,默认在项目目录下的 AssetBundles/android</param>
|
/// <param name="_output">打包完成后的输出路径</param>
|
/// <param name="_publisherString">渠道id,可打多个包,用|分割</param>
|
/// <param name="_buildIndex">打包序序号,用于标识名称</param>
|
/// <param name="_development">是否为开发版</param>
|
/// <param name="_replace">ios打包导出xcode工程模式,Append和Replace</param>
|
public static void BuildPublishers(string _sdkPath, string _assetBundlePath, string _output, string _publisherString, int _buildIndex, bool _development, bool _replace)
|
{
|
|
var publisherIds = new List<string>();
|
var tempStrings = _publisherString.Split(StringUtility.splitSeparator, StringSplitOptions.RemoveEmptyEntries);
|
|
for (int i = 0; i < tempStrings.Length; i++)
|
{
|
var temp = tempStrings[i];
|
var matches = Regex.Matches(temp, "\\d+");
|
switch (matches.Count)
|
{
|
case 1:
|
var id = int.Parse(matches[0].Value);
|
publisherIds.Add(id.ToString());
|
break;
|
case 2:
|
var min = int.Parse(matches[0].Value);
|
var max = int.Parse(matches[1].Value);
|
for (int j = min; j <= max; j++)
|
{
|
publisherIds.Add(j.ToString());
|
}
|
break;
|
}
|
}
|
|
var smallPackages = new List<string>();
|
var halfPackages = new List<string>();
|
var bigPackages = new List<string>();
|
|
for (int i = 0; i < publisherIds.Count; i++)
|
{
|
try
|
{
|
var publisher = publisherIds[i];
|
var versionName = string.Empty;
|
var versionConfig = GetVersionConfig(publisher, out versionName);
|
switch (versionConfig.assetAccess)
|
{
|
case InstalledAsset.NullAsset:
|
smallPackages.Add(publisher);
|
break;
|
case InstalledAsset.HalfAsset:
|
halfPackages.Add(publisher);
|
break;
|
case InstalledAsset.FullAsset:
|
case InstalledAsset.IngoreDownLoad:
|
bigPackages.Add(publisher);
|
break;
|
|
}
|
}
|
catch (Exception ex)
|
{
|
Debug.Log(ex);
|
}
|
}
|
|
// ========== 使用 YooAsset 打包资源 ==========
|
// 全量打包所有 Package(含 HybridCLR),自动拷贝到 StreamingAssets/yoo/{PackageName}/
|
Debug.Log("[ClientPackage] 开始 YooAsset 全量打包所有 Package...");
|
if (!YooAssetBuildTool.BuildAllPackagesCore(incremental: false))
|
{
|
Debug.LogError("[ClientPackage] YooAsset 资源打包失败,中止构建!");
|
EditorUtility.DisplayDialog("打包失败", "YooAsset 资源打包失败,请查看 Console 日志。", "确定");
|
return;
|
}
|
Debug.Log("[ClientPackage] YooAsset 资源打包完成。");
|
|
// YooAsset StreamingAssets 根目录
|
string yooRoot = AssetBundleBuilderHelper.GetStreamingAssetsRoot();
|
|
// ---- NullAsset(小包):不保留任何 Package,全部从 CDN 按需下载 ----
|
if (smallPackages.Count > 0)
|
{
|
// 删除所有 Package 文件夹
|
string[] keepPackages = { };
|
RemovePackagesExcept(yooRoot, keepPackages);
|
|
// 写入空的 Buildin 包列表,运行时所有包走纯 CacheFileSystem
|
YooAssetBuildTool.WriteBuildinPackageList();
|
|
for (int i = 0; i < smallPackages.Count; i++)
|
{
|
#if UNITY_ANDROID
|
BuildApk(_sdkPath, _output, smallPackages[i], _buildIndex, _development);
|
#elif UNITY_IOS
|
BuildIpa(_sdkPath, smallPackages[i], _buildIndex, _replace);
|
#endif
|
}
|
|
// 恢复完整 StreamingAssets 供后续使用
|
RestoreYooStreamingAssets();
|
}
|
|
// ---- HalfAsset(中包):剔除部分 Package ----
|
if (halfPackages.Count > 0)
|
{
|
// 按旧逻辑映射:hero→Battle, audio→Audio, video→Video, uieffect→UIEffect
|
var removePackages = new List<string> { "Battle", "Audio", "Video", "UIEffect" };
|
if (!includeUI) removePackages.Add("UI");
|
|
foreach (var pkgName in removePackages)
|
{
|
string pkgDir = Path.Combine(yooRoot, pkgName);
|
if (Directory.Exists(pkgDir))
|
{
|
Directory.Delete(pkgDir, true);
|
Debug.Log($"[ClientPackage] HalfAsset: 已剔除 Package '{pkgName}'");
|
}
|
}
|
|
// 写入剔除后的 Buildin 包列表
|
YooAssetBuildTool.WriteBuildinPackageList();
|
|
for (int i = 0; i < halfPackages.Count; i++)
|
{
|
#if UNITY_ANDROID
|
BuildApk(_sdkPath, _output, halfPackages[i], _buildIndex, _development);
|
#elif UNITY_IOS
|
BuildIpa(_sdkPath, halfPackages[i], _buildIndex, _replace);
|
#endif
|
}
|
|
// 恢复完整 StreamingAssets 供后续使用
|
RestoreYooStreamingAssets();
|
}
|
|
// ---- FullAsset(大包):保留所有 Package ----
|
if (bigPackages.Count > 0)
|
{
|
// 全量 StreamingAssets,写入完整 Buildin 包列表
|
YooAssetBuildTool.WriteBuildinPackageList();
|
|
for (int i = 0; i < bigPackages.Count; i++)
|
{
|
#if UNITY_ANDROID
|
BuildApk(_sdkPath, _output, bigPackages[i], _buildIndex, _development);
|
#elif UNITY_IOS
|
BuildIpa(_sdkPath, bigPackages[i], _buildIndex, _replace);
|
#endif
|
}
|
}
|
|
}
|
|
public static void BuildApk(string _sdkPath, string _output, string _publisher, int _buildIndex, bool _development)
|
{
|
PreBuild(_publisher, _buildIndex);
|
var auditTimeFile = StringUtility.Concat(Application.dataPath, Path.DirectorySeparatorChar.ToString(), "Resources/Config/AuditTime.txt");
|
if (File.Exists(auditTimeFile))
|
{
|
File.Delete(auditTimeFile);
|
}
|
|
var versionName = string.Empty;
|
var versionConfig = GetVersionConfig(_publisher, out versionName);
|
// var versionConfigCSpath = Application.dataPath + "/Scripts/System/ClientVersion/VersionConfig.cs";
|
// var text = File.ReadAllText(versionConfigCSpath);
|
|
// if (text.Contains("VERSION_ALTERNATIVE"))
|
// {
|
// var pattern = "VERSION_ALTERNATIVE = \".*\"";
|
// text = Regex.Replace(text, pattern, StringUtility.Concat("VERSION_ALTERNATIVE = ", "\"", versionConfig.m_Version, "\""));
|
|
// bool encoderShouldEmitUTF8Identifier = true;
|
// bool throwOnInvalidBytes = false;
|
// UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier, throwOnInvalidBytes);
|
// bool append = false;
|
// StreamWriter streamWriter = new StreamWriter(versionConfigCSpath, append, encoding);
|
// streamWriter.Write(text);
|
// streamWriter.Close();
|
// AssetDatabase.ImportAsset(versionConfigCSpath);
|
// }
|
|
// 针对某一些打包的特殊性, 这里特指依赖的第三方库含有资源的问题
|
// 这里对有这种特型的渠道的sdk在打包的时候差异化gradle文件
|
// 更名原有文件
|
// string _gradleTemplateUrl = EditorApplication.applicationContentsPath + "/PlaybackEngines/AndroidPlayer/Tools/GradleTemplates/";
|
// string _gradleFileUrl = _gradleTemplateUrl + "mainTemplate.gradle";
|
// string _diffGradleFileUrl = StringUtility.Concat(_sdkPath, "/GradleDiff/", versionConfig.appId, "/mainTemplate.gradle");
|
// FileInfo _fileInfo = new FileInfo(_diffGradleFileUrl);
|
|
// if (!_fileInfo.Exists)
|
// {
|
// EditorUtility.DisplayDialog("警告", "Gradle模式需要配置差异文件,但是没有找到该文件: " + _diffGradleFileUrl, "@Hale");
|
// return;
|
// }
|
|
// _fileInfo.CopyTo(_gradleFileUrl, true);
|
|
// 移动配置文件至打包工作目录中
|
var keystorePath = Application.dataPath + "/Editor/Keystore/" + versionConfig.keystoreFileName + ".keystore";
|
if (!File.Exists(keystorePath))
|
{
|
Debug.LogError("签名文件不存在");
|
return;
|
}
|
PlayerSettings.Android.keystoreName = keystorePath;
|
PlayerSettings.Android.keystorePass = versionConfig.keystorePassword;
|
PlayerSettings.Android.keyaliasName = versionConfig.keystoreAlias;
|
PlayerSettings.Android.keyaliasPass = versionConfig.keystoreAliasPassword;
|
// PlayerSettings.Android.targetSdkVersion = AndroidSdkVersions.AndroidApiLevel22;
|
PlayerSettings.Android.bundleVersionCode = VersionConfig.GetVersionNumber(versionConfig.version);
|
|
PlayerSettings.enableInternalProfiler = _development;
|
|
//删除原sdk文件
|
if (Directory.Exists(THE_2TH_WORLD_SDK_PATH))
|
Directory.Delete(THE_2TH_WORLD_SDK_PATH, true);
|
if (File.Exists(MANIFEST_PATH))
|
File.Delete(MANIFEST_PATH);
|
if (File.Exists(File_baseProjectTemplate))
|
File.Delete(File_baseProjectTemplate);
|
if (File.Exists(File_google_services))
|
File.Delete(File_google_services);
|
if (File.Exists(File_gradleTemplate))
|
File.Delete(File_gradleTemplate);
|
if (File.Exists(File_LauncherManifest))
|
File.Delete(File_LauncherManifest);
|
if (File.Exists(File_launcherTemplate))
|
File.Delete(File_launcherTemplate);
|
if (File.Exists(File_mainTemplate))
|
File.Delete(File_mainTemplate);
|
if (File.Exists(File_deps))
|
File.Delete(File_deps);
|
if (File.Exists(File_huawei_services))
|
File.Delete(File_huawei_services);
|
if (File.Exists(File_settingGradle))
|
File.Delete(File_settingGradle);
|
|
|
var variant = _development ? "debug" : "release";
|
|
string channelSdkPath;
|
string copySdkFile;
|
|
channelSdkPath = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/", variant);
|
copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/AndroidManifest.xml");
|
File.Copy(copySdkFile, MANIFEST_PATH);
|
copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/baseProjectTemplate.gradle");
|
File.Copy(copySdkFile, File_baseProjectTemplate);
|
copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/deps.gradle");
|
File.Copy(copySdkFile, File_deps);
|
// copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/google-services.json");
|
// File.Copy(copySdkFile, File_google_services);
|
copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/gradleTemplate.properties");
|
File.Copy(copySdkFile, File_gradleTemplate);
|
copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/LauncherManifest.xml");
|
File.Copy(copySdkFile, File_LauncherManifest);
|
copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/launcherTemplate.gradle");
|
File.Copy(copySdkFile, File_launcherTemplate);
|
copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/mainTemplate.gradle");
|
File.Copy(copySdkFile, File_mainTemplate);
|
copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/settingsTemplate.gradle");
|
File.Copy(copySdkFile, File_settingGradle);
|
|
if (versionConfig.sdkFileName == "hyyngame_huawei")
|
{
|
copySdkFile = StringUtility.Concat(_sdkPath, "/Channel/Android/", versionConfig.sdkFileName, "/agconnect-services.json");
|
File.Copy(copySdkFile, File_huawei_services);
|
}
|
|
Debug.LogFormat("Android 清单等文件拷贝成功");
|
|
|
|
if (Directory.Exists(channelSdkPath))
|
{
|
FileExtersion.DirectoryCopy(channelSdkPath, THE_2TH_WORLD_SDK_PATH);
|
Debug.LogFormat("执行Android 渠道SDK 文件拷贝,from:{0};to:{1}", channelSdkPath, THE_2TH_WORLD_SDK_PATH);
|
}
|
else
|
{
|
Debug.LogFormat("Android 渠道SDK文件不存在,{0}", channelSdkPath);
|
return;
|
}
|
|
//var option = AssetDatabase.LoadAssetAtPath<Options>("Assets/Editor/Beebyte/Obfuscator/ObfuscatorOptions.asset");
|
//if (option != null)
|
//{
|
// option.enabled = obfuscatorEnabled;
|
// EditorUtility.SetDirty(option);
|
//}
|
|
AssetDatabase.SaveAssets();
|
AssetDatabase.Refresh();
|
|
// 确保不以 Gradle 工程模式输出(HybridCLR StripAOT 步骤会临时置为 true,若未还原则会输出文件夹而非 APK)
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
|
|
if (_development)
|
{
|
var apkName = StringUtility.Concat(_output, "/", versionName, "_", versionConfig.clientPackageFlag, "_v", versionConfig.m_Version, "_", _buildIndex.ToString(), "_development.apk");
|
BuildPipeline.BuildPlayer(baseLevels, apkName, BuildTarget.Android, BuildOptions.Development | BuildOptions.ConnectWithProfiler | BuildOptions.AllowDebugging);
|
}
|
else
|
{
|
string apkName;
|
if (EditorUserBuildSettings.buildAppBundle)
|
{
|
Debug.Log("勾选了 Build App Bundle (Google Play) 打包aab");
|
apkName = StringUtility.Concat(_output, "/", versionName, "_", versionConfig.clientPackageFlag, "_v", versionConfig.m_Version, "_", _buildIndex.ToString(), ".aab");
|
}
|
else
|
{
|
Debug.Log("没有勾选 Build App Bundle (Google Play) 打包apk");
|
apkName = StringUtility.Concat(_output, "/", versionName, "_", versionConfig.clientPackageFlag, "_v", versionConfig.m_Version, "_", _buildIndex.ToString(), ".apk");
|
}
|
BuildPipeline.BuildPlayer(baseLevels, apkName, BuildTarget.Android, BuildOptions.None);
|
}
|
}
|
|
public static VersionConfig versionConfig1 = null;
|
|
/// <summary>
|
/// 导出Android工程给AS打包
|
/// Unity只负责导出unityLibrary,SDK/Gradle/签名等均由AS工程处理
|
/// </summary>
|
public static void ExportAndroidProject(string _publisher, int _buildIndex, bool _development, string _outputPath, string _assetBundlePath)
|
{
|
if (string.IsNullOrEmpty(_outputPath) || !Directory.Exists(_outputPath))
|
{
|
Debug.LogError("导出路径无效,请设置有效的 Android Export Path: " + _outputPath);
|
return;
|
}
|
|
var versionName = string.Empty;
|
var versionConfig = GetVersionConfig(_publisher, out versionName);
|
// 导出前清理 Assets/Plugins/Android(保留 libs 目录)
|
CleanupPluginsAndroidForExport();
|
string copySdkFile;
|
copySdkFile = StringUtility.Concat(SDK_PLUGIN_PROJECT, "/Channel/Android/", versionConfig.sdkFileName, "/AndroidManifest.xml");
|
File.Copy(copySdkFile, MANIFEST_PATH);
|
PreBuild(_publisher, _buildIndex);
|
|
|
PlayerSettings.Android.bundleVersionCode = VersionConfig.GetVersionNumber(versionConfig.version);
|
PlayerSettings.enableInternalProfiler = _development;
|
|
//完整取StreamingAssets 资源
|
|
// // 先清理StreamingAssets,再根据assetAccess配置拷贝资源到StreamingAssets
|
// var streamingPath = ResourcesPath.Instance.StreamingAssetPath;
|
// if (Directory.Exists(streamingPath))
|
// Directory.Delete(streamingPath, true);
|
|
// if (!string.IsNullOrEmpty(_assetBundlePath) && Directory.Exists(_assetBundlePath))
|
// {
|
// switch (versionConfig.assetAccess)
|
// {
|
// case InstalledAsset.NullAsset:
|
// CopyNullAssetResources(_assetBundlePath, streamingPath);
|
// break;
|
// case InstalledAsset.HalfAsset:
|
// CopyHalfAssetResources(_assetBundlePath, streamingPath);
|
// break;
|
// case InstalledAsset.FullAsset:
|
// case InstalledAsset.IngoreDownLoad:
|
// CopyFullAssetResources(_assetBundlePath, streamingPath);
|
// break;
|
// }
|
// Debug.LogFormat("资源拷贝完成({0}),从 {1} 到 {2}", versionConfig.assetAccess, _assetBundlePath, streamingPath);
|
// }
|
// else
|
// {
|
// Debug.LogWarningFormat("AssetBundle路径为空或不存在: {0},导出工程将不包含游戏资源", _assetBundlePath);
|
// }
|
|
// 关键设置:导出为Gradle工程而非直接打APK
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
|
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
|
EditorUserBuildSettings.development = _development;
|
|
// 导出路径
|
_outputPath = Path.GetFullPath(_outputPath);
|
string outputDir = Path.Combine(_outputPath, versionName + "_" + versionConfig.clientPackageFlag);
|
|
if (Directory.Exists(outputDir))
|
{
|
Directory.Delete(outputDir, true);
|
}
|
|
Debug.LogFormat("开始导出Android工程,输出路径: {0}", outputDir);
|
|
BuildPipeline.BuildPlayer(baseLevels, outputDir, BuildTarget.Android,
|
_development ? BuildOptions.AcceptExternalModificationsToPlayer | BuildOptions.Development | BuildOptions.AllowDebugging
|
: BuildOptions.AcceptExternalModificationsToPlayer);
|
|
Debug.LogFormat("导出Android工程完成!输出路径: {0}", outputDir);
|
|
// 导出完成后恢复设置
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
|
EditorUserBuildSettings.development = false;
|
}
|
|
/// <summary>
|
/// 小包拷贝:仅拷贝builtin资源
|
/// </summary>
|
static void CopyNullAssetResources(string _assetBundlePath, string _streamingPath)
|
{
|
var files = new List<FileInfo>();
|
var builtInFiles = new List<FileInfo>();
|
|
FileExtersion.GetAllDirectoryFileInfos(_assetBundlePath, files);
|
foreach (var file in files)
|
{
|
if (file.FullName.Contains("builtin"))
|
{
|
builtInFiles.Add(file);
|
}
|
}
|
|
foreach (var item in builtInFiles)
|
{
|
var extension = Path.GetExtension(item.FullName);
|
if (extension == ".meta")
|
continue;
|
|
var relativePath = FileExtersion.GetFileRelativePath(_assetBundlePath, item.FullName);
|
var to = StringUtility.Concat(_streamingPath, relativePath);
|
var directory = Path.GetDirectoryName(to);
|
if (!Directory.Exists(directory))
|
Directory.CreateDirectory(directory);
|
|
File.Copy(item.FullName, to, true);
|
}
|
}
|
|
/// <summary>
|
/// 半包拷贝:根据PriorBundleConfig剔除优先级低的hero/maps/audio/uieffect/video资源,
|
/// 以及config和ui资源(取决于includeConfig/includeUI开关)
|
/// </summary>
|
static void CopyHalfAssetResources(string _assetBundlePath, string _streamingPath)
|
{
|
PriorBundleConfig.InitForEditor(() =>
|
{
|
var fromFiles = new List<FileInfo>();
|
FileExtersion.GetAllDirectoryFileInfos(_assetBundlePath, fromFiles);
|
|
var excludeFileFullNames = new List<string>();
|
|
// 剔除优先级低的hero资源
|
var tempFiles = new List<FileInfo>();
|
FileExtersion.GetAllDirectoryFileInfos(StringUtility.Concat(_assetBundlePath, "/hero"), tempFiles);
|
foreach (var file in tempFiles)
|
{
|
var fileName = Path.GetFileNameWithoutExtension(file.FullName);
|
var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Mob, AssetVersionUtility.DecodeFileName(fileName));
|
if (prior > AssetPrior)
|
{
|
excludeFileFullNames.Add(file.FullName);
|
}
|
}
|
|
// 剔除优先级低的maps资源
|
tempFiles.Clear();
|
FileExtersion.GetAllDirectoryFileInfos(StringUtility.Concat(_assetBundlePath, "/maps"), tempFiles);
|
foreach (var file in tempFiles)
|
{
|
var fileName = Path.GetFileNameWithoutExtension(file.FullName);
|
var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Scene, AssetVersionUtility.DecodeFileName(fileName));
|
if (prior > AssetPrior)
|
{
|
excludeFileFullNames.Add(file.FullName);
|
}
|
}
|
|
// 剔除优先级低的audio资源
|
tempFiles.Clear();
|
FileExtersion.GetAllDirectoryFileInfos(StringUtility.Concat(_assetBundlePath, "/audio"), tempFiles);
|
foreach (var file in tempFiles)
|
{
|
var fileName = Path.GetFileNameWithoutExtension(file.FullName);
|
var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Audio, AssetVersionUtility.DecodeFileName(fileName));
|
if (prior > AssetPrior)
|
{
|
excludeFileFullNames.Add(file.FullName);
|
}
|
}
|
|
// 剔除优先级低的uieffect资源
|
tempFiles.Clear();
|
FileExtersion.GetAllDirectoryFileInfos(StringUtility.Concat(_assetBundlePath, "/uieffect"), tempFiles);
|
foreach (var file in tempFiles)
|
{
|
var fileName = Path.GetFileNameWithoutExtension(file.FullName);
|
var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Effect, AssetVersionUtility.DecodeFileName(fileName));
|
if (prior > AssetPrior)
|
{
|
excludeFileFullNames.Add(file.FullName);
|
}
|
}
|
|
// 剔除视频资源
|
tempFiles.Clear();
|
var videoPath = StringUtility.Concat(_assetBundlePath, "/video");
|
if (Directory.Exists(videoPath))
|
{
|
FileExtersion.GetAllDirectoryFileInfos(videoPath, tempFiles);
|
foreach (var file in tempFiles)
|
{
|
var fileName = Path.GetFileNameWithoutExtension(file.FullName);
|
var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Video, AssetVersionUtility.DecodeFileName(fileName));
|
if (prior > AssetPrior)
|
{
|
excludeFileFullNames.Add(file.FullName);
|
}
|
}
|
}
|
|
// 剔除表资源
|
if (!includeConfig)
|
{
|
tempFiles.Clear();
|
FileExtersion.GetAllDirectoryFileInfos(StringUtility.Concat(_assetBundlePath, "/config"), tempFiles);
|
foreach (var file in tempFiles)
|
{
|
excludeFileFullNames.Add(file.FullName);
|
}
|
}
|
|
// 剔除UI资源
|
if (!includeUI)
|
{
|
tempFiles.Clear();
|
FileExtersion.GetAllDirectoryFileInfos(StringUtility.Concat(_assetBundlePath, "/ui"), tempFiles);
|
foreach (var file in tempFiles)
|
{
|
excludeFileFullNames.Add(file.FullName);
|
}
|
}
|
else
|
{
|
tempFiles.Clear();
|
FileExtersion.GetAllDirectoryFileInfos(StringUtility.Concat(_assetBundlePath, "/ui"), tempFiles);
|
foreach (var file in tempFiles)
|
{
|
var fileName = Path.GetFileNameWithoutExtension(file.FullName);
|
var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.UI, fileName);
|
if (prior > AssetPrior)
|
{
|
excludeFileFullNames.Add(file.FullName);
|
}
|
}
|
}
|
|
// 从文件列表中移除需要排除的文件
|
for (int i = fromFiles.Count - 1; i >= 0; i--)
|
{
|
if (excludeFileFullNames.Contains(fromFiles[i].FullName))
|
{
|
fromFiles.RemoveAt(i);
|
}
|
}
|
|
foreach (var item in fromFiles)
|
{
|
var extension = Path.GetExtension(item.FullName);
|
if (extension == ".meta")
|
continue;
|
|
var relativePath = FileExtersion.GetFileRelativePath(_assetBundlePath, item.FullName);
|
if (relativePath.StartsWith("patch"))
|
continue;
|
|
var to = StringUtility.Concat(_streamingPath, relativePath);
|
var directory = Path.GetDirectoryName(to);
|
if (!Directory.Exists(directory))
|
Directory.CreateDirectory(directory);
|
|
File.Copy(item.FullName, to, true);
|
}
|
});
|
|
|
}
|
|
/// <summary>
|
/// 全包拷贝:拷贝所有资源(排除patch和.meta)
|
/// </summary>
|
static void CopyFullAssetResources(string _assetBundlePath, string _streamingPath)
|
{
|
var files = new List<FileInfo>();
|
FileExtersion.GetAllDirectoryFileInfos(_assetBundlePath, files);
|
foreach (var file in files)
|
{
|
var extension = Path.GetExtension(file.FullName);
|
if (extension == ".meta")
|
continue;
|
|
var relativePath = FileExtersion.GetFileRelativePath(_assetBundlePath, file.FullName);
|
if (relativePath.StartsWith("patch"))
|
continue;
|
|
var to = StringUtility.Concat(_streamingPath, relativePath);
|
var directory = Path.GetDirectoryName(to);
|
if (!Directory.Exists(directory))
|
Directory.CreateDirectory(directory);
|
|
File.Copy(file.FullName, to, true);
|
}
|
}
|
|
public static void BuildIpa(string _sdkPath, string _publisher, int _buildIndex, bool _replace)
|
{
|
PreBuild(_publisher, _buildIndex);
|
|
var versionName = string.Empty;
|
versionConfig1 = GetVersionConfig(_publisher, out versionName);
|
|
if (Directory.Exists(iOS_PLUGIN_PATH))
|
{
|
Directory.Delete(iOS_PLUGIN_PATH, true);
|
}
|
|
if (versionConfig1.sdkFileName.Trim().Length != 0)
|
{
|
FileExtersion.DirectoryCopy(StringUtility.Concat(_sdkPath, "/Channel/iOS/", versionConfig1.sdkFileName, "/Plugins"), iOS_PLUGIN_PATH);
|
}
|
else
|
{
|
FileExtersion.DirectoryCopy(StringUtility.Concat(_sdkPath, "/Channel/iOS/", versionConfig1.appId, "/Plugins"), iOS_PLUGIN_PATH);
|
}
|
|
DateTime outTime;
|
var correct = DateTime.TryParse(auditOutTime, out outTime);
|
var directory = Application.dataPath + Path.DirectorySeparatorChar + "Resources/Config";
|
if (!Directory.Exists(directory))
|
{
|
Directory.CreateDirectory(directory);
|
}
|
File.WriteAllText(Application.dataPath + Path.DirectorySeparatorChar + "Resources/Config/AuditTime.txt", correct ? auditOutTime : "");
|
|
//var option = AssetDatabase.LoadAssetAtPath<Options>("Assets/Editor/Beebyte/Obfuscator/ObfuscatorOptions.asset");
|
//if (option != null)
|
//{
|
// option.enabled = obfuscatorEnabled;
|
// EditorUtility.SetDirty(option);
|
//}
|
|
AssetDatabase.SaveAssets();
|
AssetDatabase.Refresh();
|
VersionConfig.GetAsync().ContinueWith(config =>
|
{
|
PlayerSettings.iOS.buildNumber = config.buildIndex.ToString();
|
PlayerSettings.iOS.appleDeveloperTeamID = config.appleDeveloperTeamID;
|
}).Forget();
|
|
|
#if UNITY_IOS
|
if (_replace)
|
{
|
XCodeProjectMod.BuildIOSProject_Replace();
|
}
|
else
|
{
|
XCodeProjectMod.BuildIOSProject_Append();
|
}
|
#endif
|
}
|
|
/// <summary>
|
/// 删除 YooAsset StreamingAssets 中除指定 Package 外的所有 Package 文件夹
|
/// </summary>
|
private static void RemovePackagesExcept(string yooRoot, string[] keepPackages)
|
{
|
if (!Directory.Exists(yooRoot)) return;
|
var keepSet = new HashSet<string>(keepPackages, StringComparer.OrdinalIgnoreCase);
|
foreach (var dir in Directory.GetDirectories(yooRoot))
|
{
|
string dirName = Path.GetFileName(dir);
|
if (!keepSet.Contains(dirName))
|
{
|
Directory.Delete(dir, true);
|
Debug.Log($"[ClientPackage] NullAsset: 已删除 Package '{dirName}'");
|
}
|
}
|
}
|
|
/// <summary>
|
/// 从构建产物目录重新拷贝到 StreamingAssets,恢复完整状态(供下一轮渠道使用)
|
/// </summary>
|
private static void RestoreYooStreamingAssets()
|
{
|
Debug.Log("[ClientPackage] 恢复 YooAsset StreamingAssets 到完整状态...");
|
YooAssetBuildTool.RestoreBuildOutputToStreamingAssets();
|
}
|
|
//随包安装的资源不同平台不一定可以获取FileInfo,所以需要下载一个文件来获取资源的MD5信息
|
//该文件在打包时生成 记录随包的所有资源的信息
|
//该文件记录的资源在包中肯定存在,所以不用去取指定的资源文件获得FileInfo 计算MD5
|
//通过文件和AssetsVersion.txt的文件做比较,来判断是否需要下载资源
|
//logicbytes.txt 包含元数据和代码 如果不随包也要处理
|
static void MakePackageMD5File()
|
{
|
var fileInfos = new List<FileInfo>();
|
FileExtersion.GetAllDirectoryFileInfos(ResourcesPath.Instance.StreamingAssetPath, fileInfos);
|
AssetsVersionCmpMaker.WriteAssetsVersionFile(ResourcesPath.Instance.StreamingAssetPath, fileInfos);
|
Debug.Log("--生成AssetsVersionCmp.txt文件完毕");
|
}
|
|
private static void PreBuild(string _publisher, int _buildIndex)
|
{
|
MakePackageMD5File();
|
var newVersionConfigPath = StringUtility.Concat("Assets/Resources/VersionConfig", ".asset");
|
var versionName = string.Empty;
|
var fromVersionConfig = GetVersionConfig(_publisher, out versionName);
|
var newVersionConfig = ScriptableObject.CreateInstance<VersionConfig>();
|
if (File.Exists(newVersionConfigPath))
|
{
|
AssetDatabase.DeleteAsset(newVersionConfigPath);
|
}
|
|
VersionConfig.Copy(fromVersionConfig, newVersionConfig);
|
newVersionConfig.buildTime = DateTime.Now.ToString("yy/MM/dd--HH:mm");
|
newVersionConfig.buildIndex = _buildIndex;
|
|
if (File.Exists(Application.dataPath + "/Resources/Game.txt"))
|
{
|
File.Delete(Application.dataPath + "/Resources/Game.txt");
|
}
|
|
File.WriteAllText(Application.dataPath + "/Resources/Game.txt", GetGameId(_publisher));
|
|
AssetDatabase.CreateAsset(newVersionConfig, newVersionConfigPath);
|
EditorUtility.SetDirty(newVersionConfig);
|
|
//生成VersionConfigEx.txt 提供给AOT启动时使用
|
var json = JsonUtility.ToJson(newVersionConfig);
|
string versionConfigExFile = Application.dataPath + "/Resources/VersionConfigEx.txt";
|
if (File.Exists(versionConfigExFile))
|
{
|
File.Delete(versionConfigExFile);
|
}
|
File.WriteAllText(versionConfigExFile, json);
|
|
|
SetIconAndSplashImage(versionName);
|
//SetCreateRoleAnimation();
|
|
PlayerSettings.companyName = "xmsw";
|
PlayerSettings.productName = newVersionConfig.productName;
|
PlayerSettings.applicationIdentifier = newVersionConfig.bundleIdentifier;
|
PlayerSettings.defaultInterfaceOrientation = UIOrientation.Portrait;
|
PlayerSettings.allowedAutorotateToLandscapeLeft = true;
|
PlayerSettings.allowedAutorotateToLandscapeRight = true;
|
PlayerSettings.allowedAutorotateToPortrait = false;
|
PlayerSettings.allowedAutorotateToPortraitUpsideDown = false;
|
PlayerSettings.bundleVersion = VersionConfig.GetVersionNumber(newVersionConfig.version).ToString();
|
|
|
AssetDatabase.SaveAssets();
|
AssetDatabase.Refresh();
|
}
|
|
public static VersionConfig GetVersionConfig(string _publisher, out string _versionName)
|
{
|
var lines = File.ReadAllLines(versionsFilePath);
|
|
for (int i = 2; i < lines.Length; i++)
|
{
|
var line = lines[i];
|
var lineStrings = line.Split('\t');
|
if (lineStrings[0] == _publisher)
|
{
|
var config = new VersionConfig();
|
config.Read(line);
|
_versionName = lineStrings[1];
|
if (!string.IsNullOrEmpty(lineStrings[24]))
|
_versionName = lineStrings[24];
|
return config;
|
}
|
}
|
|
_versionName = string.Empty;
|
return null;
|
}
|
|
static string GetGameId(string _publisher)
|
{
|
var lines = File.ReadAllLines(versionsFilePath);
|
for (int i = 2; i < lines.Length; i++)
|
{
|
var line = lines[i];
|
var lineStrings = line.Split('\t');
|
if (lineStrings[0] == _publisher)
|
{
|
return lineStrings[7];
|
}
|
}
|
|
return "xssg";
|
}
|
|
static void SetIconAndSplashImage(string _versionName)
|
{
|
var buildTarget = GetBuildTarget();
|
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(StringUtility.Concat("Assets/Editor/Logo/", _versionName, "/Icon.png"));
|
var iconSizes = PlayerSettings.GetIconSizesForTargetGroup(buildTarget);
|
var icons = new Texture2D[iconSizes.Length];
|
for (int i = 0; i < iconSizes.Length; i++)
|
{
|
icons[i] = texture;
|
}
|
|
PlayerSettings.SetIconsForTargetGroup(buildTarget, icons);
|
|
PlayerSettings.SplashScreen.show = false;
|
PlayerSettings.SplashScreen.showUnityLogo = false;
|
PlayerSettings.SplashScreen.unityLogoStyle = PlayerSettings.SplashScreen.UnityLogoStyle.LightOnDark;
|
|
var splashImage = AssetDatabase.LoadAssetAtPath<Sprite>(StringUtility.Concat("Assets/Editor/Logo/", _versionName, "/SplashImage.png"));
|
var splashScreenLogo = PlayerSettings.SplashScreenLogo.Create(3, splashImage);
|
PlayerSettings.SplashScreen.logos = new PlayerSettings.SplashScreenLogo[] { splashScreenLogo };
|
|
if (buildTarget == BuildTargetGroup.Android)
|
{
|
var projectSettings = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/ProjectSettings.asset")[0]);
|
var splashImageSetting = projectSettings.FindProperty("androidSplashScreen");
|
splashImageSetting.objectReferenceValue = splashImage.texture;
|
projectSettings.ApplyModifiedProperties();
|
|
PlayerSettings.Android.splashScreenScale = AndroidSplashScreenScale.ScaleToFill;
|
}
|
}
|
|
public static void SetCreateRoleAnimation()
|
{
|
var from = "Assets/Editor/Video/CreateRoleMovie.mp4";
|
var to = "Assets/StreamingAssets/CreateRoleMovie.mp4";
|
|
if (!Directory.Exists(Application.dataPath + "StreamingAssets"))
|
{
|
Directory.CreateDirectory(Application.dataPath + "StreamingAssets");
|
}
|
|
if (AssetDatabase.LoadAssetAtPath<UnityEngine.Video.VideoClip>(to) == null)
|
{
|
AssetDatabase.CopyAsset(from, to);
|
}
|
}
|
|
/// <summary>
|
/// 导出 Gradle 工程前清理 Assets/Plugins/Android(保留 libs 目录)
|
/// </summary>
|
static void CleanupPluginsAndroidForExport()
|
{
|
var androidPluginPath = Path.Combine(Application.dataPath, "Plugins/Android");
|
if (!Directory.Exists(androidPluginPath))
|
return;
|
|
Debug.Log("开始清理 Assets/Plugins/Android(保留 libs 目录)...");
|
|
// 删除所有子目录(除了 libs)
|
var subDirs = Directory.GetDirectories(androidPluginPath);
|
foreach (var dir in subDirs)
|
{
|
var dirName = Path.GetFileName(dir);
|
if (dirName != "libs")
|
{
|
Directory.Delete(dir, true);
|
Debug.LogFormat("已删除 Plugins/Android/{0} 目录", dirName);
|
}
|
}
|
|
// 删除根目录下的所有文件(保留 .aar 和 .aar.meta)
|
var rootFiles = Directory.GetFiles(androidPluginPath);
|
foreach (var file in rootFiles)
|
{
|
var fileName = Path.GetFileName(file);
|
if (fileName.EndsWith(".aar") || fileName.EndsWith(".aar.meta"))
|
{
|
Debug.LogFormat("已保留文件: {0}", fileName);
|
continue;
|
}
|
File.Delete(file);
|
Debug.LogFormat("已删除文件: {0}", fileName);
|
}
|
|
|
}
|
|
static BuildTargetGroup GetBuildTarget()
|
{
|
#if UNITY_ANDROID
|
return BuildTargetGroup.Android;
|
#elif UNITY_IOS
|
return BuildTargetGroup.iOS;
|
#else
|
return BuildTargetGroup.Standalone;
|
#endif
|
}
|
|
}
|