using System; using System.IO; using System.Collections.Generic; using System.Linq; using AIGame.EditorTools; using HotUpdate.UI; using Newtonsoft.Json.Linq; using TMPro; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.EventSystems; using System.Reflection; using System.Threading.Tasks; /// Pipeline-only import, JSON-to-Prefab build, isolated rendering and binding checks. public static class BuildHomeUi { static string Repo => Directory.GetParent(Application.dataPath).Parent.FullName; const string Folder = "Assets/Bundles/UI/Texture/Home"; static readonly string[] Icons = { "coin", "diamond", "energy", "avatar", "tasks", "gift", "mail", "trophy", "bag", "home", "battle", "pets", "chest", "calendar", "settings", "pinball" }; static readonly string[] Names = { "UI_MainHUD" }; static string Design => Path.Combine(Repo, "Doc/Design/UI/Home"); public static string Build() { if (EditorApplication.isPlaying) throw new Exception("Build in Edit Mode."); Directory.CreateDirectory(Folder); File.Copy(Path.Combine(Design, "home_camp_source.png"), Folder + "/home_camp.png", true); var atlas = new Texture2D(2, 2, TextureFormat.RGBA32, false); try { if (!atlas.LoadImage(File.ReadAllBytes(Path.Combine(Design, "home_icons_source.png")))) throw new Exception("Invalid icon sheet."); for (int i = 0; i < Icons.Length; i++) { int col = i % 4, row = 3 - i / 4; int x = Mathf.RoundToInt(col * atlas.width / 4f), y = Mathf.RoundToInt(row * atlas.height / 4f); int w = Mathf.RoundToInt((col + 1) * atlas.width / 4f) - x; int h = Mathf.RoundToInt((row + 1) * atlas.height / 4f) - y; var icon = new Texture2D(w, h, TextureFormat.RGBA32, false); try { icon.SetPixels(CleanSlice(atlas.GetPixels(x, y, w, h), w, h)); icon.Apply(); File.WriteAllBytes(Folder + "/home_ico_" + Icons[i] + ".png", icon.EncodeToPNG()); } finally { UnityEngine.Object.DestroyImmediate(icon); } } } finally { UnityEngine.Object.DestroyImmediate(atlas); } AssetDatabase.Refresh(); foreach (var path in Directory.GetFiles(Folder, "*.png")) { var importer = (TextureImporter)AssetImporter.GetAtPath(path); importer.textureType = TextureImporterType.Sprite; importer.spriteImportMode = SpriteImportMode.Single; importer.spritePixelsPerUnit = 100; var settings = new TextureImporterSettings(); importer.ReadTextureSettings(settings); settings.spriteMeshType = SpriteMeshType.FullRect; importer.SetTextureSettings(settings); importer.alphaIsTransparency = true; importer.mipmapEnabled = false; importer.textureCompression = TextureImporterCompression.Uncompressed; importer.filterMode = FilterMode.Bilinear; importer.wrapMode = TextureWrapMode.Clamp; importer.maxTextureSize = 2048; importer.SaveAndReimport(); } foreach (var name in Names) { var path = Path.Combine(Repo, "Doc/UIPrefabCreater", name + ".json"); var validation = JsonUIPrefabBuilder.Validate(path); if (!validation.Ok) throw new Exception(string.Join("\n", validation.Errors)); if (string.IsNullOrEmpty(JsonUIPrefabBuilder.BuildFromJsonFile(path))) throw new Exception("Build failed: " + name); } return "Imported 17 Home sprites; built Candy home HUD prefab from JSON."; } // Atlas extraction: discard isolated fragments spilling in from adjacent cells. // This preserves original icon pixels and alpha; it does not repaint the source artwork. static Color[] CleanSlice(Color[] pixels, int width, int height) { var visited = new bool[pixels.Length]; var components = new List>(); for (int start = 0; start < pixels.Length; start++) { if (visited[start] || pixels[start].a < 0.01f) continue; var queue = new Queue(); var component = new List(); queue.Enqueue(start); visited[start] = true; while (queue.Count > 0) { int p = queue.Dequeue(); component.Add(p); int px = p % width, py = p / width; for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++) { int nx = px + dx, ny = py + dy; if (nx < 0 || ny < 0 || nx >= width || ny >= height) continue; int next = ny * width + nx; if (visited[next] || pixels[next].a < 0.01f) continue; visited[next] = true; queue.Enqueue(next); } } components.Add(component); } int largest = components.Count > 0 ? components.Max(c => c.Count) : 0; foreach (var c in components.Where(c => c.Count < largest * 0.03f)) foreach (int p in c) pixels[p] = Color.clear; return pixels; } public static string Verify() { int checks = 0; Action check = (ok, message) => { if (!ok) throw new Exception(message); checks++; }; foreach (var name in Names) { var doc = JObject.Parse(File.ReadAllText(Path.Combine(Repo, "Doc/UIPrefabCreater", name + ".json"))); var prefab = AssetDatabase.LoadAssetAtPath("Assets/Bundles/UI/Prefab/" + name + ".prefab"); check(prefab != null, "Missing prefab " + name); var scene = EditorSceneManager.NewPreviewScene(); var go = UnityEngine.Object.Instantiate(prefab); SceneManager.MoveGameObjectToScene(go, scene); try { foreach (var path in ((JObject)doc["assets"]["sprites"]).Properties()) check(AssetDatabase.LoadAssetAtPath((string)path.Value) != null, "Missing sprite " + path.Name); var images = go.GetComponentsInChildren(true); check(images.All(i => i.sprite != null), "Unresolved prefab image " + name); check(go.GetComponentsInChildren().Single().referenceResolution == new Vector2(1080, 1920), "Wrong resolution"); foreach (var b in doc["bindings"]) check(go.GetComponentsInChildren(true).Any(t => t.name == (string)b), "Missing binding " + b); check(!go.GetComponentsInChildren(true).Any(t => t.name == "UI_ThemeSwitcher"), "Unexpected theme toolbar"); var view = go.AddComponent(); view.Bind(); string called = null; int invocations = 0, reconnect = 0; view.OnHomeActionClicked += k => { called = k; invocations++; }; view.OnReconnectClicked += () => reconnect++; view.Bind(); // Bind is safe to repeat. foreach (var e in doc["events"]) { var button = go.GetComponentsInChildren