Land Explorer in HomeCamp with boot loading, journal UI, and CDN publish tooling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-11-04 16:18:01 +08:00
co-authored by Cursor
parent 9eec364f30
commit 80678ec499
405 changed files with 48053 additions and 420302 deletions
@@ -107,6 +107,16 @@ namespace AOT.Editor.YooAssetTools
AssetPathToGUIDOptions.OnlyExistingAssets))).ToArray())
BundleCollectorSettingData.RemoveCollector(group, collector);
const string campScene = "Assets/Bundles/Scenes/Town/HomeCamp/HomeCamp.unity";
if (string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(campScene, AssetPathToGUIDOptions.OnlyExistingAssets)))
throw new InvalidOperationException("HomeCamp scene is missing: " + campScene);
if (!group.Collectors.Any(c => c.CollectPath == campScene))
BundleCollectorSettingData.CreateCollector(group, new BundleCollector {
CollectPath = campScene, CollectorGUID = AssetDatabase.AssetPathToGUID(campScene),
CollectorType = ECollectorType.MainAssetCollector, AddressRuleName = nameof(AddressByFileName),
PackRuleName = nameof(PackSeparately), FilterRuleName = nameof(CollectScene), AssetTags = "art"
});
package.CheckConfigError();
BundleCollectorSettingData.SaveFile();
return "ok scene entries=" + group.Collectors.Count;
@@ -173,7 +173,7 @@ namespace AOT.Editor.YooAssetTools
var prod = VersionJson(packVersion, resVersion, PublicCdnRoot);
WriteText(Path.Combine(CdnRoot, Channel, "version.json"), local);
WriteText(Path.Combine(CdnRoot, Channel, "version.prod.json"), prod);
WriteText(Path.Combine(Application.streamingAssetsPath, "cdn", "version.json"), local);
WriteText(Path.Combine(Application.streamingAssetsPath, "cdn", "version.json"), prod);
}
static string VersionJson(string packVersion, string resVersion, string cdnRoot)
@@ -0,0 +1,111 @@
#if UNITY_EDITOR
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace AOT.Editor.YooAssetTools
{
/// <summary>Explicit, incremental publication of the current WebGL resource packages.</summary>
public static class CdnPublisher
{
static bool _busy;
static string Repo => Path.GetFullPath(Path.Combine(Application.dataPath, "../.."));
static string Config => Path.Combine(Repo, "Tools/cdn_publish.local.json");
static string LogPath => Path.Combine(Repo, "Tools/output/cdn_publish/last-unity.log");
public static string Status { get; private set; } = "idle";
[MenuItem("AIGame/CDN/Check SSH Connection")]
public static async void CheckSshMenu() => await Guard(() => RunPython("check"));
[MenuItem("AIGame/CDN/Preview Upload Changes")]
public static async void PreviewMenu() => await Guard(() => RunPython("plan --compare"));
[MenuItem("AIGame/CDN/Upload Staged Resources")]
public static async void UploadMenu() => await Guard(() => RunPython("upload"));
[MenuItem("AIGame/CDN/Build And Upload (Art + HotUpdate)")]
public static async void BuildAndUploadMenu() => await Guard(async () => {
await RunPython("check");
BuildPackages();
await RunPython("upload");
});
[MenuItem("AIGame/CDN/Prepare SSH Key")]
public static async void PrepareKeyMenu() => await Guard(() => RunPython("init-key"));
[MenuItem("AIGame/CDN/Open Publish Folder")]
public static void OpenPublishFolder()
{
Directory.CreateDirectory(Path.GetDirectoryName(LogPath));
EditorUtility.RevealInFinder(Path.GetDirectoryName(LogPath));
}
public static string BuildPackages()
{
if (EditorApplication.isPlaying || EditorApplication.isCompiling)
throw new InvalidOperationException("Stop Play Mode and finish compiling before building CDN resources.");
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.WebGL)
throw new InvalidOperationException("CDN packages must be built for WebGL. Select that target first.");
Status = "building Art";
var art = BuildAndStageArt.BuildConfiguredAndStage();
if (!art.StartsWith("ok", StringComparison.Ordinal)) throw new InvalidOperationException(art);
Debug.Log("[CDN] " + art);
Status = "building HotUpdate";
var raw = BuildAndStageHotUpdate.BuildAndStage();
if (!raw.StartsWith("ok", StringComparison.Ordinal)) throw new InvalidOperationException(raw);
Debug.Log("[CDN] " + raw);
Status = "packages staged";
return art + " | " + raw;
}
static async Task Guard(Func<Task> action)
{
if (_busy) { Debug.LogWarning("[CDN] A publish command is already running."); return; }
_busy = true;
try { await action(); Status = "completed"; Debug.Log("[CDN] Completed. Log: " + LogPath); }
catch (Exception error) { Status = "failed"; Debug.LogError("[CDN] " + error.Message); }
finally { _busy = false; }
}
static async Task RunPython(string command)
{
if (!File.Exists(Config))
File.Copy(Path.Combine(Repo, "Tools/cdn_publish.example.json"), Config);
Status = command;
var pending = new ConcurrentQueue<string>();
var start = new ProcessStartInfo {
FileName = EditorPrefs.GetString("AIGame.CdnPython", "python"),
Arguments = "\"" + Path.Combine(Repo, "Tools/publish_cdn.py") + "\" " + command + " --config \"" + Config + "\"",
WorkingDirectory = Repo, UseShellExecute = false, CreateNoWindow = true,
RedirectStandardOutput = true, RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8
};
start.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";
start.EnvironmentVariables["PYTHONUNBUFFERED"] = "1";
Directory.CreateDirectory(Path.GetDirectoryName(LogPath));
using (var log = new StreamWriter(LogPath, true, new UTF8Encoding(false)))
using (var process = new Process { StartInfo = start })
{
log.WriteLine(DateTime.UtcNow.ToString("o") + " " + command);
process.OutputDataReceived += (_, e) => { if (e.Data != null) pending.Enqueue(e.Data); };
process.ErrorDataReceived += (_, e) => { if (e.Data != null) pending.Enqueue(e.Data); };
process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine();
while (!process.HasExited)
{
while (pending.TryDequeue(out var line)) { Debug.Log("[CDN] " + line); log.WriteLine(line); }
log.Flush(); await Task.Delay(150);
}
process.WaitForExit();
while (pending.TryDequeue(out var line)) { Debug.Log("[CDN] " + line); log.WriteLine(line); }
if (process.ExitCode != 0) throw new InvalidOperationException("Publish command failed. See " + LogPath);
}
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fe8f0110cd9a3084eb1da66ea0ee89c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: