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
@@ -160,6 +160,7 @@ namespace AOT.Editor.WeChat
try
{
WeChatLoadingCover.Configure(mini);
JObject.Parse(File.ReadAllText(Path.Combine(mini, "game.json")));
string miniConfigPath = Path.Combine(mini, "project.config.json");
string rootConfigPath = Path.Combine(dst, "project.config.json");
@@ -196,6 +197,8 @@ namespace AOT.Editor.WeChat
static string ValidateBootOnly()
{
string startupError = AIGame.EditorTools.BuildBootLoading.ValidateConfigured();
if (startupError != null) return startupError;
if (!File.Exists(BootScene))
return "缺少场景 " + BootScene;
var enabled = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(scene => scene.path).ToArray();
@@ -0,0 +1,78 @@
#if UNITY_EDITOR
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using AIGame.EditorTools;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace AOT.Editor.WeChat
{
public static class WeChatLoadingCover
{
public static void Configure(string mini)
{
string repo = Path.GetFullPath(Path.Combine(Application.dataPath,"../.."));
string module = Path.Combine(repo,"Tools/WeChat/startup-loading.js");
string gamePath = Path.Combine(mini,"game.js");
string game = File.ReadAllText(gamePath);
const string import = "import startupLoading from './startup-loading';";
if (!game.Contains(import))
{
var pattern = new Regex(@"loadingPageConfig:\s*\{.*?(?=\r?\n\s{8}\}\);)",RegexOptions.Singleline);
if (pattern.Matches(game).Count != 1) throw new InvalidOperationException("Cannot identify SDK loading-page configuration.");
game = pattern.Replace(game,"loadingPageConfig: startupLoading,",1);
game = import + Environment.NewLine + game;
}
if (!game.Contains("loadingPageConfig: startupLoading,")) throw new InvalidOperationException("SDK cover import has no configuration.");
string output = Path.Combine(repo,"Tools/output/startup_presentation/startup-cover.png");
Render(output);
Directory.CreateDirectory(Path.Combine(mini,"images"));
File.Copy(output,Path.Combine(mini,"images/startup-cover.png"),true);
File.Copy(module,Path.Combine(mini,"startup-loading.js"),true);
File.WriteAllText(gamePath,game,new UTF8Encoding(false));
}
static void Render(string path)
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(BuildBootLoading.PrefabPath);
if (prefab == null) throw new InvalidOperationException("Build the local loading prefab first.");
var active = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene,NewSceneMode.Additive);
var previous = RenderTexture.active;
var target = new RenderTexture(720,1280,24);
var pixels = new Texture2D(720,1280,TextureFormat.RGB24,false);
try
{
var root = (GameObject)PrefabUtility.InstantiatePrefab(prefab,scene);
root.transform.Find("LoadingCard").gameObject.SetActive(false);
var cameraObject = new GameObject("CoverRenderCamera",typeof(Camera));
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(cameraObject,scene);
var camera = cameraObject.GetComponent<Camera>();
camera.targetTexture=target;camera.cullingMask=1<<LayerMask.NameToLayer("UI");
camera.clearFlags=CameraClearFlags.SolidColor;camera.backgroundColor=new Color(.54f,.72f,.76f);
var canvas = root.GetComponent<Canvas>();
canvas.renderMode=RenderMode.ScreenSpaceCamera;canvas.worldCamera=camera;canvas.planeDistance=1;
Canvas.ForceUpdateCanvases();
foreach(var text in root.GetComponentsInChildren<TMP_Text>()) text.ForceMeshUpdate();
camera.Render();RenderTexture.active=target;
pixels.ReadPixels(new Rect(0,0,720,1280),0,0);pixels.Apply();
var colors = pixels.GetPixels32(); var first=colors[0]; int varied=0;
foreach(var color in colors)
if(Mathf.Abs(color.r-first.r)+Mathf.Abs(color.g-first.g)+Mathf.Abs(color.b-first.b)>30) varied++;
if(varied<colors.Length/10) throw new InvalidOperationException("Loading cover render is blank.");
Directory.CreateDirectory(Path.GetDirectoryName(path));File.WriteAllBytes(path,pixels.EncodeToPNG());
}
finally
{
RenderTexture.active=previous;EditorSceneManager.CloseScene(scene,true);
if (active.IsValid() && active.isLoaded) UnityEngine.SceneManagement.SceneManager.SetActiveScene(active);
target.Release();UnityEngine.Object.DestroyImmediate(target);UnityEngine.Object.DestroyImmediate(pixels);
}
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bb291bc5c1cac5646885b8c13a0013a2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: