TheInitialProject/Assets/CaoCao/Scripts/Editor/XAsset/Build/Config/Settings.cs
2024-10-23 16:59:02 +08:00

423 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using CaoCao.XAsset;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace CaoCao.Editor
{
public enum PlayerAssetsSplitMode
{
SplitByAssetPacksWithInstallTime,
IncludeAllAssets,
ExcludeAllAssets
}
[CreateAssetMenu(fileName = nameof(Settings), menuName = "CaoCao/XAsset/" + nameof(Settings))]
public class Settings : ScriptableObject
{
[Header("Player")] public string downloadURL = "http://127.0.0.1/";
[Tooltip("大版本号,不一样则需要下载新包")]
public int BigVersion = 0;
[Tooltip("安装包资源分离模式: \n" +
"Split By Asset Packs With Install Time:安装包中只包含交付模式为 Install Time 的 Asset Packs 中的资源。 \n" +
"Include All Assets 安装包中包含所有资源。 \n" +
"Exclude All Assets 安装包中不包含资源。")]
public PlayerAssetsSplitMode playerAssetsSplitMode = PlayerAssetsSplitMode.IncludeAllAssets;
/// <summary>
/// 是否将安装包内的资源二次打包Android 设备上开启这个选项可以优化 IO 效率。
/// </summary>
[Tooltip("是否为 AssetPack 配置中的资源生成实体文件")]
public bool packPlayerAssets = true;
/// <summary>
/// 离线模式,运行时有效,开启后不会触发更新。
/// </summary>
[Tooltip("离线模式,运行时有效,开启后不会触发更新")]
public bool offlineMode;
/// <summary>
/// 查找依赖引用输出的文件名
/// </summary>
[Tooltip("查找依赖引用输出的文件名")]
public string findReferencesInBuildPath = "FindReferencesInBuild.txt";
/// <summary>
/// 打包 bundle 的设置
/// </summary>
public BundleSettings bundle = new BundleSettings();
/// <summary>
/// 编辑器仿真模式,开启后,无需打包可以进入播放模式。
/// </summary>
[Header("Development")] public bool simulationMode = true;
/// <summary>
/// 编辑器下是否开启仿真下载模式,开启后,无需把资源部署到服务器,就行运行真机的更新过程。
/// </summary>
public bool simulationDownload = true;
/// <summary>
/// 编辑器仿真模式下,是否采集所有资源,需要更长的初始化耗时,但是可以及时对未采集的内容进行预警。
/// </summary>
public bool collectAllAssets;
/// <summary>
/// 最大并行下载数量
/// </summary>
[Header("Download")] [Range(1, 10)] public byte maxDownloads = 5;
/// <summary>
/// 最大错误重试次数
/// </summary>
[Range(0, 5)] public byte maxRetryTimes = 3;
private static string Filename => $"Assets/GameAssets/Config/XAsset/{nameof(Settings)}.asset";
public static BuildGroup GetAutoGroup()
{
return GetOrCreateAsset<BuildGroup>("Assets/GameAssets/Config/XAsset/Auto.asset", group =>
{
group.bundleMode = BundleMode.PackByFile;
group.addressMode = AddressMode.LoadByDependencies;
});
}
public PlayerAssets GetPlayerAssets()
{
var assets = CreateInstance<PlayerAssets>();
assets.updateInfoURL = $"{downloadURL}{Assets.Bundles}/{Platform}/{UpdateInfo.Filename}";
assets.downloadURL = $"{downloadURL}{Assets.Bundles}/{Platform}";
assets.offlineMode = offlineMode;
assets.packed = packPlayerAssets;
assets.maxDownloads = maxDownloads;
assets.maxRetryTimes = maxRetryTimes;
return assets;
}
public static string PlatformCachePath =>
$"{Environment.CurrentDirectory}/{Assets.Bundles}Cache/{Platform}".Replace('\\', '/');
public static string PlatformDataPath =>
$"{Environment.CurrentDirectory.Replace('\\', '/')}/{Assets.Bundles}/{Platform}";
public static Platform Platform => GetPlatform();
private static Settings defaultSettings;
public static Settings GetDefaultSettings()
{
if (defaultSettings != null)
return defaultSettings;
var assets = FindAssets<Settings>();
defaultSettings = assets.Length > 0
? assets[0]
: GetOrCreateAsset<Settings>(Filename);
return defaultSettings;
}
public static void FindReferencesInBuild()
{
var selection = Selection.GetFiltered<Object>(SelectionMode.DeepAssets);
var sb = new StringBuilder();
foreach (var o in selection)
{
var asset = GetAsset(AssetDatabase.GetAssetPath(o));
sb.AppendLine(asset.path);
sb.AppendLine(" - References By");
foreach (var s in asset.referencesBy) sb.AppendLine($" - {s}");
}
var settings = GetDefaultSettings();
if (File.Exists(settings.findReferencesInBuildPath))
File.Delete(settings.findReferencesInBuildPath);
File.WriteAllText(settings.findReferencesInBuildPath, sb.ToString());
EditorUtility.OpenWithDefaultApp(settings.findReferencesInBuildPath);
}
public static Versions GetDefaultVersions()
{
var versions = XAsset.Utility.LoadFromFile<Versions>(GetCachePath(Versions.Filename));
return versions;
}
public static string GetDataPath(string filename)
{
return $"{PlatformDataPath}/{filename}";
}
public static string GetCachePath(string filename)
{
return $"{PlatformCachePath}/{filename}";
}
private static Platform GetPlatform()
{
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
switch (EditorUserBuildSettings.activeBuildTarget)
{
case BuildTarget.Android:
return Platform.Android;
case BuildTarget.StandaloneOSX:
return Platform.OSX;
case BuildTarget.StandaloneWindows:
case BuildTarget.StandaloneWindows64:
return Platform.Windows;
case BuildTarget.iOS:
return Platform.iOS;
case BuildTarget.WebGL:
return Platform.WebGL;
case BuildTarget.StandaloneLinux64:
return Platform.Linux;
default:
return Platform.Default;
}
}
public static long GetLastWriteTime(string path)
{
var file = new FileInfo(path);
return file.Exists ? file.LastAccessTime.ToFileTime() : 0;
}
public static string[] GetDependenciesWithoutCache(string assetPath)
{
var set = new HashSet<string>();
set.UnionWith(AssetDatabase.GetDependencies(assetPath));
set.Remove(assetPath);
var exclude = GetDefaultSettings().bundle.excludeFiles;
// Unity 会存在场景依赖场景的情况。
set.RemoveWhere(s => s.EndsWith(".unity") || exclude.Exists(s.EndsWith));
return set.ToArray();
}
public static T GetOrCreateAsset<T>(string path, Action<T> onCreate = null) where T : ScriptableObject
{
var asset = AssetDatabase.LoadAssetAtPath<T>(path);
if (asset != null)
return asset;
XAsset.Utility.CreateDirectoryIfNecessary(path);
asset = CreateInstance<T>();
AssetDatabase.CreateAsset(asset, path);
onCreate?.Invoke(asset);
return asset;
}
public static T[] FindAssets<T>() where T : ScriptableObject
{
var builds = new List<T>();
var guilds = AssetDatabase.FindAssets("t:" + typeof(T).FullName);
foreach (var guild in guilds)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guild);
if (string.IsNullOrEmpty(assetPath)) continue;
var asset = AssetDatabase.LoadAssetAtPath<T>(assetPath);
if (asset == null) continue;
builds.Add(asset);
}
return builds.ToArray();
}
public static BundleSettings BundleSettings => GetDefaultSettings().bundle;
public static string extension => BundleSettings.extension;
/// <summary>
/// [path, entry, bundle, group, return(bundle)]
/// </summary>
public static Func<string, string, string, BuildGroup, string> customPacker { get; set; } = null;
public static Func<string, bool> customFilter { get; set; } = s => true;
private static string GetDirectoryName(string path)
{
var dir = Path.GetDirectoryName(path);
return !string.IsNullOrEmpty(dir) ? dir.Replace("\\", "/") : string.Empty;
}
public static string PackAsset(BuildAsset asset)
{
var assetPath = asset.path;
var group = asset.group;
if (group == null)
{
asset.addressMode = AddressMode.LoadByDependencies;
return "auto";
}
var entry = asset.entry;
var bundle = group.name.ToLower();
var dir = GetDirectoryName(entry) + "/";
assetPath = assetPath.Replace(dir, "");
entry = entry.Replace(dir, "");
switch (group.bundleMode)
{
case BundleMode.PackTogether:
bundle = group.name;
break;
case BundleMode.PackByFolder:
bundle = GetDirectoryName(assetPath);
break;
case BundleMode.PackByFile:
bundle = assetPath;
break;
case BundleMode.PackByTopSubFolder:
if (!string.IsNullOrEmpty(entry))
{
var pos = assetPath.IndexOf("/", entry.Length + 1, StringComparison.Ordinal);
bundle = pos != -1 ? assetPath.Substring(0, pos) : entry;
}
else
{
CaoCao.XAsset.Logger.E($"invalid rootPath {assetPath}");
}
break;
case BundleMode.PackByRaw:
bundle = assetPath;
break;
case BundleMode.PackByEntry:
bundle = Path.GetFileNameWithoutExtension(entry);
break;
case BundleMode.PackByCustom:
if (customPacker != null) bundle = customPacker?.Invoke(assetPath, entry, bundle, group);
break;
default:
throw new ArgumentOutOfRangeException();
}
return PackAsset(assetPath, bundle, group.build);
}
public static string PackAsset(string assetPath, string bundle, string build)
{
var settings = GetDefaultSettings().bundle;
if (settings.packTogetherForAllShaders && settings.shaderExtensions.Exists(assetPath.EndsWith))
bundle = "shaders";
if (settings.packByFileForAllScenes && assetPath.EndsWith(".unity"))
bundle = assetPath;
bundle = bundle.Replace(" ", "").Replace("/", "_").Replace("-", "_").Replace(".", "_").ToLower();
bundle += settings.extension;
if (!string.IsNullOrEmpty(build) && settings.splitBundleNameWithBuild)
return $"{build.ToLower()}/{bundle}";
return $"{bundle}";
}
public static BuildAsset GetAsset(string path)
{
return GetBuildAssetCache().GetAsset(path);
}
private static BuildAssetCache BuildAssetCache;
public static string[] GetDependencies(string assetPath)
{
return GetBuildAssetCache().GetDependencies(assetPath);
}
public static BuildAssetCache GetBuildAssetCache()
{
if (BuildAssetCache != null)
return BuildAssetCache;
var path = AssetDatabase.GetAssetPath(GetDefaultSettings());
var dir = Path.GetDirectoryName(path);
BuildAssetCache = GetOrCreateAsset<BuildAssetCache>($"{dir}/{nameof(BuildAssetCache)}.asset");
return BuildAssetCache;
}
private static BuildEntryCache BuildEntryCache;
public static BuildEntryCache GetBuildEntryCache()
{
if (BuildEntryCache != null) return BuildEntryCache;
var path = AssetDatabase.GetAssetPath(GetDefaultSettings());
var dir = Path.GetDirectoryName(path);
BuildEntryCache = GetOrCreateAsset<BuildEntryCache>($"{dir}/{nameof(BuildEntryCache)}.asset");
return BuildEntryCache;
}
public static BuildAsset[] Collect(BuildGroup group)
{
var assets = new List<BuildAsset>();
if (group.entries == null) return assets.ToArray();
foreach (var entry in group.entries) GetAssets(group, entry, assets);
return assets.ToArray();
}
private static void GetAssets(BuildGroup group, Object entry, List<BuildAsset> assets)
{
var path = AssetDatabase.GetAssetPath(entry);
if (string.IsNullOrEmpty(path)) return;
var cache = GetBuildEntryCache();
var entryAsset = cache.GetEntry(path, group);
assets.AddRange(entryAsset.assets);
}
public static void Collect(AssetPack config, ISet<string> assets)
{
foreach (var asset in config.assets)
switch (asset)
{
case BuildGroup group:
{
foreach (var groupAsset in Collect(group))
assets.Add(groupAsset.path);
break;
}
case Build build:
foreach (var group in build.parameters.groups)
foreach (var groupAsset in Collect(group))
assets.Add(groupAsset.path);
break;
default:
var assetPath = AssetDatabase.GetAssetPath(asset);
if (Directory.Exists(assetPath))
{
var guilds = AssetDatabase.FindAssets("", new[]
{
assetPath
});
foreach (var guild in guilds)
{
var child = AssetDatabase.GUIDToAssetPath(guild);
if (string.IsNullOrEmpty(child) || Directory.Exists(child) || assets.Contains(child))
continue;
assets.Add(child);
}
}
else
{
assets.Add(assetPath);
}
break;
}
}
}
}