423 lines
16 KiB
C#
423 lines
16 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
} |