89 lines
5.3 KiB
C#
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 });
|
|
}
|
|
}
|