Add hot-update boot pipeline, UI bundles/atlas assets, and design docs.
Ignore AIClient/Library and AIClient/HybridCLRData permanently; include HybridCLR/YooAsset project setup and UI tooling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ddfb40b9e8aa2134f8393d62252209e1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
#if UNITY_EDITOR
|
||||
using HybridCLR.Editor;
|
||||
using HybridCLR.Editor.Settings;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AOT.Editor.HybridCLR
|
||||
{
|
||||
/// <summary>
|
||||
/// 将 HotUpdate.Runtime 登记为 HybridCLR 热更程序集。
|
||||
/// </summary>
|
||||
public static class ConfigureHotUpdateAssemblies
|
||||
{
|
||||
const string HotUpdateAsmdef = "Assets/HotUpdate/Scripts/HotUpdate.Runtime.asmdef";
|
||||
|
||||
[MenuItem("AIGame/HybridCLR/Configure HotUpdate Assemblies")]
|
||||
public static string Configure()
|
||||
{
|
||||
var asmdef = AssetDatabase.LoadAssetAtPath<UnityEditorInternal.AssemblyDefinitionAsset>(HotUpdateAsmdef);
|
||||
if (asmdef == null)
|
||||
return $"missing asmdef: {HotUpdateAsmdef}";
|
||||
|
||||
var settings = HybridCLRSettings.Instance;
|
||||
settings.hotUpdateAssemblyDefinitions = new[] { asmdef };
|
||||
HybridCLRSettings.Save();
|
||||
AssetDatabase.SaveAssets();
|
||||
return $"ok HotUpdateAssemblies=[{asmdef.name}] enable={settings.enable}";
|
||||
}
|
||||
|
||||
public static string Status()
|
||||
{
|
||||
var settings = HybridCLRSettings.Instance;
|
||||
int n = settings.hotUpdateAssemblyDefinitions?.Length ?? 0;
|
||||
string names = n == 0
|
||||
? "(none)"
|
||||
: string.Join(",", System.Array.ConvertAll(settings.hotUpdateAssemblyDefinitions, a => a != null ? a.name : "null"));
|
||||
return $"enable={settings.enable} count={n} defs=[{names}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e606fddadecaef64e8894556f34e5893
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Pipeline / 菜单:创建 Boot 场景并写入 Build Settings。
|
||||
/// </summary>
|
||||
public static class SetupBootScene
|
||||
{
|
||||
const string ScenePath = "Assets/Scenes/Boot.unity";
|
||||
|
||||
[MenuItem("AIGame/Boot/Setup Boot Scene")]
|
||||
public static string Setup()
|
||||
{
|
||||
var scene = UnityEditor.SceneManagement.EditorSceneManager.NewScene(
|
||||
UnityEditor.SceneManagement.NewSceneSetup.DefaultGameObjects,
|
||||
UnityEditor.SceneManagement.NewSceneMode.Single);
|
||||
|
||||
// 竖屏参考:调整主相机背景
|
||||
var cam = Camera.main;
|
||||
if (cam != null)
|
||||
{
|
||||
cam.backgroundColor = new Color(0.08f, 0.09f, 0.12f, 1f);
|
||||
cam.clearFlags = CameraClearFlags.SolidColor;
|
||||
}
|
||||
|
||||
var bootGo = new GameObject("Boot");
|
||||
bootGo.AddComponent<AOT.Boot.BootDriver>();
|
||||
|
||||
Directory.CreateDirectory("Assets/Scenes");
|
||||
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(scene, ScenePath);
|
||||
|
||||
var scenes = new[]
|
||||
{
|
||||
new EditorBuildSettingsScene(ScenePath, true),
|
||||
};
|
||||
// 保留其它场景但 Boot 置顶
|
||||
var existing = EditorBuildSettings.scenes;
|
||||
var list = new System.Collections.Generic.List<EditorBuildSettingsScene> { scenes[0] };
|
||||
foreach (var s in existing)
|
||||
{
|
||||
if (s.path != ScenePath)
|
||||
list.Add(s);
|
||||
}
|
||||
EditorBuildSettings.scenes = list.ToArray();
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
return $"ok scene={ScenePath} buildScenes={EditorBuildSettings.scenes.Length}";
|
||||
}
|
||||
|
||||
public static string EnsurePortrait()
|
||||
{
|
||||
PlayerSettings.defaultInterfaceOrientation = UIOrientation.Portrait;
|
||||
PlayerSettings.allowedAutorotateToPortrait = true;
|
||||
PlayerSettings.allowedAutorotateToPortraitUpsideDown = false;
|
||||
PlayerSettings.allowedAutorotateToLandscapeLeft = false;
|
||||
PlayerSettings.allowedAutorotateToLandscapeRight = false;
|
||||
PlayerSettings.companyName = "XWorld";
|
||||
PlayerSettings.productName = "AIGame";
|
||||
PlayerSettings.bundleVersion = "1.0.0";
|
||||
return $"orientation={PlayerSettings.defaultInterfaceOrientation} product={PlayerSettings.productName} ver={PlayerSettings.bundleVersion}";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d499aef8485eca1498241f05e2742d6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8be9d8bbcab6e504da0a1afb4d7aba6f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,105 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AIGame.EditorTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Unity 获得焦点时自动转换 Doc/UIPrefabCreater 中变更的 UI JSON。
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public static class JsonUIAutoConvert
|
||||
{
|
||||
private const string PrefKey = "AIGame.UI.AutoConvertJsonOnFocus";
|
||||
private static bool s_Busy;
|
||||
private static double s_LastTime;
|
||||
|
||||
static JsonUIAutoConvert()
|
||||
{
|
||||
EditorApplication.focusChanged += OnFocusChanged;
|
||||
}
|
||||
|
||||
private static void OnFocusChanged(bool focused)
|
||||
{
|
||||
if (!focused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
AutoConvertOnFocus();
|
||||
}
|
||||
|
||||
[MenuItem("Tools/UI/Convert Changed JSON Prefabs Now", false, 200)]
|
||||
public static void ConvertChangedNow()
|
||||
{
|
||||
ConvertChanged(false, "manual");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/UI/Auto Convert JSON On Focus", false, 202)]
|
||||
public static void ToggleAutoConvert()
|
||||
{
|
||||
bool enabled = !IsEnabled();
|
||||
EditorPrefs.SetBool(PrefKey, enabled);
|
||||
Menu.SetChecked("Tools/UI/Auto Convert JSON On Focus", enabled);
|
||||
Debug.Log("[UIGen] Auto convert JSON on focus: " + (enabled ? "enabled" : "disabled"));
|
||||
}
|
||||
|
||||
[MenuItem("Tools/UI/Auto Convert JSON On Focus", true)]
|
||||
public static bool ToggleAutoConvertValidate()
|
||||
{
|
||||
Menu.SetChecked("Tools/UI/Auto Convert JSON On Focus", IsEnabled());
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void AutoConvertOnFocus()
|
||||
{
|
||||
if (!IsEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ConvertChanged(false, "focus");
|
||||
}
|
||||
|
||||
private static bool IsEnabled()
|
||||
{
|
||||
if (EditorPrefs.HasKey(PrefKey))
|
||||
{
|
||||
return EditorPrefs.GetBool(PrefKey, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ConvertChanged(bool force, string reason)
|
||||
{
|
||||
if (s_Busy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!force && EditorApplication.timeSinceStartup - s_LastTime < 1.0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (EditorApplication.isCompiling || EditorApplication.isUpdating || EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_Busy = true;
|
||||
s_LastTime = EditorApplication.timeSinceStartup;
|
||||
try
|
||||
{
|
||||
string jsonRoot = JsonUIPrefabBuilder.ResolveDefaultJsonRoot();
|
||||
JsonUIPrefabBuilder.BuildChanged(jsonRoot, reason, force);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
s_Busy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf0a5bf71c3566f4688e2aa6fdd7b230
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7519c9fb16ec144ebe9339123b49c5b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 930ed1c075c6ef74e94f60f3981cbe3f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,139 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.IO;
|
||||
using HybridCLR.Editor;
|
||||
using HybridCLR.Editor.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using YooAsset;
|
||||
using YooAsset.Editor;
|
||||
|
||||
namespace AOT.Editor.YooAssetTools
|
||||
{
|
||||
/// <summary>
|
||||
/// CompileDll → 拷贝到 Bundles/Raw → RawFile 构建 → 发布到 Tools/cdn_root + StreamingAssets。
|
||||
/// </summary>
|
||||
public static class BuildAndStageHotUpdate
|
||||
{
|
||||
public const string PackageName = SetupBundleCollector.PackageName;
|
||||
public const string AppVersion = "1.0.0";
|
||||
public const string Channel = "wechat";
|
||||
|
||||
static string RepoRoot => Path.GetFullPath(Path.Combine(Application.dataPath, "..", ".."));
|
||||
static string CdnRoot => Path.Combine(RepoRoot, "Tools", "cdn_root");
|
||||
static string ProjectBundlesRoot => BundleBuilderHelper.GetDefaultBuildOutputRoot();
|
||||
|
||||
[MenuItem("AIGame/YooAsset/Build And Stage (Raw + CDN)")]
|
||||
public static string BuildAndStage()
|
||||
{
|
||||
SetupBundleCollector.Setup();
|
||||
|
||||
var target = EditorUserBuildSettings.activeBuildTarget;
|
||||
CompileDllCommand.CompileDll(target, false);
|
||||
|
||||
var dllSrc = Path.Combine(SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target), "HotUpdate.Runtime.dll");
|
||||
if (!File.Exists(dllSrc))
|
||||
return $"fail: missing compiled dll: {dllSrc}";
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(SetupBundleCollector.DllBytesPath) ?? "Assets/Bundles/Raw");
|
||||
File.Copy(dllSrc, SetupBundleCollector.DllBytesPath, overwrite: true);
|
||||
AssetDatabase.ImportAsset(SetupBundleCollector.DllBytesPath, ImportAssetOptions.ForceUpdate);
|
||||
|
||||
string packageVersion = DateTime.Now.ToString("yyyyMMdd.HHmmss");
|
||||
var buildResult = BuildRawPackage(target, packageVersion);
|
||||
if (!buildResult.Success)
|
||||
return $"fail build: {buildResult.ErrorInfo}";
|
||||
|
||||
string packageDir = buildResult.OutputPackageDirectory;
|
||||
string stageDir = Path.Combine(CdnRoot, Channel, AppVersion, PackageName);
|
||||
StageDirectory(packageDir, stageDir);
|
||||
|
||||
string publicCdnRoot = $"https://www.xworld.ren/{Channel}/{AppVersion}/{PackageName}";
|
||||
string localCdnRoot = $"http://127.0.0.1:8787/{Channel}/{AppVersion}/{PackageName}";
|
||||
// 本地 serve 用 local;上传前用 Tools/cdn_publish.py write-version 切到 public
|
||||
WriteVersionJson(Path.Combine(CdnRoot, Channel, "version.json"), packageVersion, localCdnRoot);
|
||||
WriteVersionJson(Path.Combine(CdnRoot, Channel, "version.prod.json"), packageVersion, publicCdnRoot);
|
||||
WriteVersionJson(Path.Combine(Application.streamingAssetsPath, "cdn", "version.json"), packageVersion, localCdnRoot);
|
||||
|
||||
WritePublishMeta(packageVersion, packageDir, stageDir, dllSrc);
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
return $"ok ver={packageVersion} packageDir={packageDir} stage={stageDir} dllBytes={new FileInfo(SetupBundleCollector.DllBytesPath).Length}";
|
||||
}
|
||||
|
||||
static BuildResult BuildRawPackage(BuildTarget target, string packageVersion)
|
||||
{
|
||||
var buildParameters = new RawFileBuildParameters
|
||||
{
|
||||
BuildOutputRoot = ProjectBundlesRoot,
|
||||
BundledFileRoot = BundleBuilderHelper.GetStreamingAssetsRoot(),
|
||||
BuildPipeline = EBuildPipeline.RawFileBuildPipeline.ToString(),
|
||||
BuildBundleType = (int)EBundleType.RawBundle,
|
||||
BuildTarget = target,
|
||||
PackageName = PackageName,
|
||||
PackageVersion = packageVersion,
|
||||
VerifyBuildingResult = true,
|
||||
FileNameStyle = EFileNameStyle.HashName,
|
||||
// 拷贝到 StreamingAssets/yoo,便于 OfflinePlayMode 冒烟
|
||||
BundledCopyOption = EBundledCopyOption.ClearAndCopyAll,
|
||||
BundledCopyParams = string.Empty,
|
||||
ClearBuildCacheFiles = false,
|
||||
UseAssetDependencyDB = true,
|
||||
};
|
||||
|
||||
var pipeline = new RawFileBuildPipeline();
|
||||
return pipeline.Run(buildParameters, true);
|
||||
}
|
||||
|
||||
static void StageDirectory(string src, string dst)
|
||||
{
|
||||
if (Directory.Exists(dst))
|
||||
Directory.Delete(dst, true);
|
||||
Directory.CreateDirectory(dst);
|
||||
|
||||
foreach (var file in Directory.GetFiles(src, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
var rel = file.Substring(src.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
var destFile = Path.Combine(dst, rel);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destFile) ?? dst);
|
||||
File.Copy(file, destFile, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteVersionJson(string path, string resVersion, string cdnRoot)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path) ?? ".");
|
||||
// JsonUtility 不支持 string[],手写 JSON
|
||||
var json =
|
||||
"{\n" +
|
||||
$" \"appVersionMin\": \"{AppVersion}\",\n" +
|
||||
$" \"appVersionMax\": \"{AppVersion}\",\n" +
|
||||
$" \"resVersion\": \"{resVersion}\",\n" +
|
||||
$" \"cdnRoot\": \"{cdnRoot}\",\n" +
|
||||
$" \"packages\": [ \"{PackageName}\" ],\n" +
|
||||
" \"forceUpdate\": false,\n" +
|
||||
" \"notice\": \"\"\n" +
|
||||
"}\n";
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
|
||||
static void WritePublishMeta(string resVersion, string packageDir, string stageDir, string dllSrc)
|
||||
{
|
||||
var metaPath = Path.Combine(CdnRoot, Channel, "last_publish.json");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(metaPath) ?? CdnRoot);
|
||||
var json =
|
||||
"{\n" +
|
||||
$" \"resVersion\": \"{resVersion}\",\n" +
|
||||
$" \"appVersion\": \"{AppVersion}\",\n" +
|
||||
$" \"packageDir\": \"{Escape(packageDir)}\",\n" +
|
||||
$" \"stageDir\": \"{Escape(stageDir)}\",\n" +
|
||||
$" \"dllSrc\": \"{Escape(dllSrc)}\",\n" +
|
||||
$" \"builtAt\": \"{DateTime.Now:o}\"\n" +
|
||||
"}\n";
|
||||
File.WriteAllText(metaPath, json);
|
||||
}
|
||||
|
||||
static string Escape(string path) => (path ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b90533025225c0149a438a078fe71011
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
#if UNITY_EDITOR
|
||||
using AOT.Boot;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using YooAsset;
|
||||
|
||||
namespace AOT.Editor.YooAssetTools
|
||||
{
|
||||
/// <summary>
|
||||
/// 切换 BootDriver 的 M1 验证模式(Offline / Host / Direct)。
|
||||
/// </summary>
|
||||
public static class ConfigureBootPlayMode
|
||||
{
|
||||
const string BootScene = "Assets/Scenes/Boot.unity";
|
||||
|
||||
[MenuItem("AIGame/Boot/Mode/Editor Direct (M0)")]
|
||||
public static string SetDirect() => Apply(
|
||||
EPlayMode.EditorSimulateMode,
|
||||
editorDirect: true,
|
||||
versionUrl: "https://www.xworld.ren/wechat/version.json",
|
||||
defaultCdn: "https://www.xworld.ren/wechat/1.0.0/DefaultPackage");
|
||||
|
||||
[MenuItem("AIGame/Boot/Mode/Offline Smoke (M1)")]
|
||||
public static string SetOffline() => Apply(EPlayMode.OfflinePlayMode, editorDirect: false);
|
||||
|
||||
[MenuItem("AIGame/Boot/Mode/Host Local CDN (M1)")]
|
||||
public static string SetHostLocal() => Apply(EPlayMode.HostPlayMode, editorDirect: false,
|
||||
versionUrl: "http://127.0.0.1:8787/wechat/version.json",
|
||||
defaultCdn: "http://127.0.0.1:8787/wechat/1.0.0/DefaultPackage");
|
||||
|
||||
static string Apply(EPlayMode playMode, bool editorDirect, string versionUrl = null, string defaultCdn = null)
|
||||
{
|
||||
var scene = EditorSceneManager.OpenScene(BootScene, OpenSceneMode.Single);
|
||||
var boot = Object.FindObjectOfType<BootDriver>();
|
||||
if (boot == null)
|
||||
return "fail: BootDriver not found in Boot scene";
|
||||
|
||||
var so = new SerializedObject(boot);
|
||||
var settings = so.FindProperty("settings");
|
||||
settings.FindPropertyRelative("PlayMode").enumValueIndex = (int)playMode;
|
||||
settings.FindPropertyRelative("EditorDirectHotUpdate").boolValue = editorDirect;
|
||||
if (!string.IsNullOrEmpty(versionUrl))
|
||||
settings.FindPropertyRelative("VersionUrl").stringValue = versionUrl;
|
||||
if (!string.IsNullOrEmpty(defaultCdn))
|
||||
settings.FindPropertyRelative("DefaultCdnRoot").stringValue = defaultCdn;
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
EditorSceneManager.MarkSceneDirty(scene);
|
||||
EditorSceneManager.SaveScene(scene);
|
||||
return $"ok playMode={playMode} editorDirect={editorDirect} versionUrl={settings.FindPropertyRelative("VersionUrl").stringValue}";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63c6c0b31c0ce5040a28cb40125ae1ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,93 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using YooAsset.Editor;
|
||||
|
||||
namespace AOT.Editor.YooAssetTools
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化 DefaultPackage 收集配置(Raw 热更 DLL)。
|
||||
/// </summary>
|
||||
public static class SetupBundleCollector
|
||||
{
|
||||
public const string PackageName = "DefaultPackage";
|
||||
public const string CollectPath = "Assets/Bundles/Raw";
|
||||
public const string DllBytesPath = "Assets/Bundles/Raw/HotUpdate.Runtime.bytes";
|
||||
|
||||
[MenuItem("AIGame/YooAsset/Setup Bundle Collector")]
|
||||
public static string Setup()
|
||||
{
|
||||
Directory.CreateDirectory(CollectPath);
|
||||
EnsurePlaceholderDllBytes();
|
||||
|
||||
var setting = BundleCollectorSettingData.Setting;
|
||||
setting.ShowPackageView = true;
|
||||
setting.UniqueBundleName = false;
|
||||
|
||||
var package = setting.Packages.FirstOrDefault(p => p.PackageName == PackageName);
|
||||
if (package == null)
|
||||
package = BundleCollectorSettingData.CreatePackage(PackageName);
|
||||
|
||||
package.PackageDesc = "启动必需:热更 DLL 等";
|
||||
package.EnableAddressable = true;
|
||||
package.SupportExtensionless = true;
|
||||
package.LocationToLower = false;
|
||||
package.IncludeAssetGUID = false;
|
||||
package.AutoCollectShaders = false;
|
||||
package.IgnoreRuleName = nameof(RawFileIgnoreRule);
|
||||
BundleCollectorSettingData.ModifyPackage(package);
|
||||
|
||||
var group = package.Groups.FirstOrDefault(g => g.GroupName == "HotUpdateDll");
|
||||
if (group == null)
|
||||
group = BundleCollectorSettingData.CreateGroup(package, "HotUpdateDll");
|
||||
group.GroupDesc = "HybridCLR 热更程序集";
|
||||
group.ActiveRuleName = nameof(EnableGroup);
|
||||
group.AssetTags = "aot";
|
||||
BundleCollectorSettingData.ModifyGroup(package, group);
|
||||
|
||||
var collector = group.Collectors.FirstOrDefault(c => c.CollectPath == CollectPath);
|
||||
if (collector == null)
|
||||
{
|
||||
collector = new BundleCollector
|
||||
{
|
||||
CollectPath = CollectPath,
|
||||
CollectorGUID = AssetDatabase.AssetPathToGUID(CollectPath),
|
||||
CollectorType = ECollectorType.MainAssetCollector,
|
||||
AddressRuleName = nameof(AddressByFileName),
|
||||
PackRuleName = nameof(PackRawFile),
|
||||
FilterRuleName = nameof(CollectAll),
|
||||
AssetTags = "aot",
|
||||
};
|
||||
BundleCollectorSettingData.CreateCollector(group, collector);
|
||||
}
|
||||
else
|
||||
{
|
||||
collector.CollectorGUID = AssetDatabase.AssetPathToGUID(CollectPath);
|
||||
collector.CollectorType = ECollectorType.MainAssetCollector;
|
||||
collector.AddressRuleName = nameof(AddressByFileName);
|
||||
collector.PackRuleName = nameof(PackRawFile);
|
||||
collector.FilterRuleName = nameof(CollectAll);
|
||||
collector.AssetTags = "aot";
|
||||
BundleCollectorSettingData.ModifyCollector(group, collector);
|
||||
}
|
||||
|
||||
package.CheckConfigError();
|
||||
BundleCollectorSettingData.SaveFile();
|
||||
return $"ok package={PackageName} collect={CollectPath} dll={DllBytesPath} setting={AssetDatabase.GetAssetPath(setting)}";
|
||||
}
|
||||
|
||||
public static void EnsurePlaceholderDllBytes()
|
||||
{
|
||||
if (File.Exists(DllBytesPath))
|
||||
return;
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(DllBytesPath) ?? CollectPath);
|
||||
// 占位:真实内容由 BuildAndStageHotUpdate 覆盖
|
||||
File.WriteAllBytes(DllBytesPath, System.Text.Encoding.UTF8.GetBytes("PLACEHOLDER_HOTUPDATE_DLL"));
|
||||
AssetDatabase.ImportAsset(DllBytesPath, ImportAssetOptions.ForceUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 011b17d87b02c034abdc5cad22378c26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user