Files
AIGame/AIClient/AgentScripts/VerifyPrototypePanelBindings.cs
T

89 lines
5.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HotUpdate.Gameplay;
using Newtonsoft.Json;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Object = UnityEngine.Object;
public static class VerifyPrototypePanelBindings
{
static readonly Type PanelType = typeof(PrototypeFlow).GetNestedType("Panel", BindingFlags.NonPublic);
static object Bind(GameObject go) => Activator.CreateInstance(PanelType, new object[] { go });
static void Check(bool condition, string message) { if (!condition) throw new Exception(message); }
public static string All()
{
var scene = EditorSceneManager.NewPreviewScene();
var root = new GameObject("PanelBindingRegression", typeof(RectTransform), typeof(Canvas));
SceneManager.MoveGameObjectToScene(root, scene);
root.GetComponent<Canvas>().renderMode = RenderMode.ScreenSpaceOverlay;
var failures = new List<string>();
int passed = 0, generated = 0;
var paths = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets/Bundles/UI/Prefab" })
.Select(AssetDatabase.GUIDToAssetPath)
.Where(p => System.IO.Path.GetFileName(p).StartsWith("UI_Prototype", StringComparison.Ordinal)).ToArray();
try
{
foreach (string path in paths)
{
var go = Object.Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>(path), root.transform, false);
try
{
var texts = go.GetComponentsInChildren<TextMeshProUGUI>(true);
Check(texts.Length >= 2, path + ": expected multiple labels");
foreach (var text in texts) text.ForceMeshUpdate(true, true);
// Use TMP's own factory: two labels can render with the same material,
// creating identically named internal nodes, as on the mini game.
var font = texts[0].font;
var material = texts[0].fontSharedMaterial;
var reference = new MaterialReference(1, font, null, material, 0);
TMP_SubMeshUI.AddSubTextObject(texts[0], reference);
TMP_SubMeshUI.AddSubTextObject(texts[1], reference);
generated += go.GetComponentsInChildren<TMP_SubMeshUI>(true).Length;
object panel = Bind(go);
var nodes = (Dictionary<string, Transform>)PanelType.GetField("_nodes", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(panel);
var labels = (Dictionary<string, TextMeshProUGUI>)PanelType.GetField("_texts", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(panel);
Check(nodes.Values.All(n => n.GetComponent<TMP_SubMeshUI>() == null), path + ": internal renderer leaked into bindings");
foreach (var node in go.GetComponentsInChildren<Transform>(true).Where(n => n.GetComponent<TMP_SubMeshUI>() == null))
Check((Transform)PanelType.GetMethod("Node").Invoke(panel, new object[] { node.name }) == node, path + ": missing node " + node.name);
foreach (var text in texts.Where(t => t.name.StartsWith("Txt_", StringComparison.Ordinal)))
{
Check(labels[text.name] == text, path + ": text lookup failed " + text.name);
if (text.GetComponentInParent<TMP_InputField>() != null) continue;
string value = text.text + " ";
PanelType.GetMethod("Text").Invoke(panel, new object[] { text.name.Substring(4), value });
Check(text.text == value, path + ": text binding failed " + text.name);
}
foreach (var button in go.GetComponentsInChildren<Button>(true))
Check((Button)PanelType.GetMethod("Button").Invoke(panel, new object[] { button.name.Substring(4) }) == button, path + ": button binding failed " + button.name);
go.SetActive(false); go.SetActive(true);
Bind(go); // Cached prefab can be reopened after TMP children already exist.
// Keep genuine authoring mistakes visible instead of silently choosing a node.
var duplicate = new GameObject(texts[0].name, typeof(RectTransform));
duplicate.transform.SetParent(go.transform, false);
bool rejected = false;
try { Bind(go); }
catch (TargetInvocationException error) { rejected = error.InnerException is ArgumentException; }
Check(rejected, path + ": duplicate authored node silently accepted");
passed++;
}
catch (Exception error) { failures.Add(path + ": " + (error.InnerException ?? error).Message); }
finally { Object.DestroyImmediate(go); }
}
}
finally { EditorSceneManager.ClosePreviewScene(scene); }
Check(paths.Length > 0, "No prototype prefabs found");
Check(failures.Count == 0, string.Join("\n", failures));
return JsonConvert.SerializeObject(new { passed, total = paths.Length, generatedTmpChildren = generated, failures });
}
}