using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using AIGame.EditorTools; using AOT.Boot; using AOT.UI; using HotUpdate.Gameplay; using HotUpdate.UI; using Newtonsoft.Json; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using Object = UnityEngine.Object; public static class VerifyHomeMobileUi { const string MaterialPath = "Assets/Bundles/UI/Material/HomeExplorerCutout.mat"; const string SceneKey = "AIGame.HomeMobile.SceneSetup"; static string Repo => Path.GetFullPath(Path.Combine(Application.dataPath, "../..")); static string Output => Path.Combine(Repo, "Tools/output/home_mobile"); public static string Status() => JsonConvert.SerializeObject(new { playing = EditorApplication.isPlaying, manager = UiManager.Instance != null, hud = UiManager.Instance != null && UiManager.Instance.Get("UI_MainHUD") != null, bootstraps = Object.FindObjectsOfType().Select(b => new { b.name, b.enabled, active = b.gameObject.activeInHierarchy }).ToArray(), explorers = Object.FindObjectsOfType().Select(e => new { e.name, e.Ready, image = e.GetComponent()?.material?.shader?.name }).ToArray(), backgrounds = Object.FindObjectsOfType().Where(i => i.name == "Img_Camp").Select(i => new { sprite = i.sprite != null ? i.sprite.name : null, texture = i.sprite != null && i.sprite.texture != null ? i.sprite.texture.name : null, textureSize = i.sprite != null && i.sprite.texture != null ? i.sprite.texture.width : 0, i.color }).ToArray(), roots = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects().Select(g => g.name).ToArray() }); public static string BuildPrefab() { Check(!EditorApplication.isPlaying, "Stop Play Mode before authoring"); if (!AssetDatabase.IsValidFolder("Assets/Bundles/UI/Material")) AssetDatabase.CreateFolder("Assets/Bundles/UI", "Material"); var shader = AssetDatabase.LoadAssetAtPath("Assets/HotUpdate/Shaders/HomeExplorerCutout.shader"); Check(shader != null && shader.isSupported, "Preview shader unavailable"); var material = AssetDatabase.LoadAssetAtPath(MaterialPath); if (material == null) { material = new Material(shader); AssetDatabase.CreateAsset(material, MaterialPath); } material.shader = shader; EditorUtility.SetDirty(material); AssetDatabase.SaveAssets(); string json = Path.Combine(Repo, "Doc/UIPrefabCreater/UI_MainHUD.json"); var validation = JsonUIPrefabBuilder.Validate(json); Check(validation.Ok, string.Join("\n", validation.Errors)); string path = JsonUIPrefabBuilder.BuildFromJsonFile(json); var prefab = AssetDatabase.LoadAssetAtPath(path); Check(prefab != null, "HUD build failed"); Check(prefab.transform.Find("ExplorerStage").GetComponent().material == material, "HUD has no explicit preview material"); Check(AssetDatabase.GetDependencies(path, true).Contains(AssetDatabase.GetAssetPath(shader)), "Shader is not a real prefab dependency"); Check(prefab.GetComponentsInChildren(true).Length == 4, "Missing safe-area components"); Check(prefab.transform.Find("Img_Camp").GetComponent().sprite != null, "Camp background sprite is missing"); VerifyCutout(material); return "PASS: JSON HUD, explicit material/shader dependency, four authored safe-area layouts and GPU color-key discard"; } static void VerifyCutout(Material material) { var texture = new Texture2D(2, 2, TextureFormat.RGBA32, false, true); var target = new RenderTexture(8, 8, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); var pixels = new Texture2D(8, 8, TextureFormat.RGBA32, false, true); var previous = RenderTexture.active; try { texture.SetPixels(Enumerable.Repeat(material.GetColor("_Key"), 4).ToArray()); texture.Apply(); target.Create(); RenderTexture.active = target; GL.Clear(true, true, Color.cyan); Graphics.Blit(texture, target, material); RenderTexture.active = target; pixels.ReadPixels(new Rect(0, 0, 8, 8), 0, 0); pixels.Apply(); Color color = pixels.GetPixel(4, 4); Check(color.r < .05f && color.b > .95f && color.g > .95f, "Color-key background was not discarded"); Graphics.Blit(Texture2D.whiteTexture, target, material); RenderTexture.active = target; pixels.ReadPixels(new Rect(0, 0, 8, 8), 0, 0); pixels.Apply(); Check(pixels.GetPixel(4, 4).r > .95f, "Foreground was discarded"); } finally { RenderTexture.active = previous; target.Release(); Object.DestroyImmediate(target); Object.DestroyImmediate(texture); Object.DestroyImmediate(pixels); } } public static string PrepareScene() { Check(!EditorApplication.isPlaying, "Stop Play Mode first"); for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++) Check(!UnityEngine.SceneManagement.SceneManager.GetSceneAt(i).isDirty, "Existing scene has unsaved changes"); Directory.CreateDirectory(Output); var setup = EditorSceneManager.GetSceneManagerSetup(); string json = JsonConvert.SerializeObject(setup); SessionState.SetString(SceneKey, json); File.WriteAllText(Path.Combine(Output, "scene_setup.json"), json); EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); SetResolution(1080, 1920, true); return "Prepared isolated scene; original scene setup and Game View size retained"; } public static string StartPreview() { Check(EditorApplication.isPlaying, "Enter Play Mode first"); var camera = GameObject.Find("HomeMobileTestCamera")?.GetComponent(); if (camera == null) camera = new GameObject("HomeMobileTestCamera", typeof(Camera)).GetComponent(); camera.clearFlags = CameraClearFlags.SolidColor; camera.cullingMask = 0; camera.backgroundColor = new Color(.2f, .3f, .4f); if (camera.GetComponent() == null) camera.gameObject.AddComponent(); if (EventSystem.current == null) new GameObject("EventSystem", typeof(EventSystem), typeof(StandaloneInputModule)); var bootstrap = Object.FindObjectOfType(); if (bootstrap == null) { bootstrap = new GameObject("GameplayBootstrap").AddComponent(); bootstrap.Bind(new BootContext { HttpBase = "http://127.0.0.1:1", UiTheme = "candy" }, null); } return "Isolated UI started; allow normal editor frames before checking"; } public static string CheckReady() { var hud = UiManager.Instance?.Get("UI_MainHUD"); Check(hud != null && hud.GetComponentInChildren().Ready, "UI preview is still loading"); Check(Object.FindObjectsOfType().Length == 0, "Startup still creates 3D world spawners"); Check(GameObject.Find("CaoCao_Runtime") == null, "Startup world was loaded"); var raw = hud.GetComponentInChildren(); Check(raw.enabled && raw.texture is RenderTexture && raw.material.shader.name == "AIGame/HomeExplorerCutout", "Live preview material missing"); var presenter = hud.GetComponentInChildren(); Check(!presenter.Talking, "Explorer speaks before a tap"); return "PASS: no startup 3D world; actual serialized preview material loaded; no automatic speech"; } public static string SetPlain() => SetScenario("plain", 1080, 1920, Vector4.zero); public static string SetPhone() => SetScenario("iphone_notch_capsule", 1080, 2338, new Vector4(0, 34f / 844, 0, 91f / 844)); public static string SetAndroid() => SetScenario("android_capsule", 1080, 2400, new Vector4(0, 0, 0, 64f / 800)); static string SetScenario(string name, int width, int height, Vector4 insets) { SetResolution(width, height, false); UiSafeArea.SetPreviewInsets(insets); SessionState.SetString(SceneKey + ".Scenario", name); SessionState.SetInt(SceneKey + ".Width", width); SessionState.SetInt(SceneKey + ".Height", height); SessionState.SetFloat(SceneKey + ".Top", insets.w); return name + " selected; allow normal editor frames before capture"; } public static string CompletePreview() { var hud = UiManager.Instance.Get("UI_MainHUD"); var presenter = hud.GetComponentInChildren(); hud.transform.Find("Btn_Explorer").GetComponent