Files

121 lines
7.3 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AIGame.EditorTools;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
/// <summary>Pipeline-only migration and verification of the Common nine-slice controls.</summary>
public static class FixUiNineSlice
{
static string Repo => Directory.GetParent(Application.dataPath).Parent.FullName;
static readonly string[] Names = { "UI_MainHUD", "UI_ConfirmDialog" };
public static string Apply()
{
if (EditorApplication.isPlayingOrWillChangePlaymode) throw new Exception("Use Edit Mode.");
int changed = UiSpriteNineSliceRules.ApplyExisting();
foreach (string name in Names)
{
string path = Path.Combine(Repo, "Doc/UIPrefabCreater", name + ".json");
if (string.IsNullOrEmpty(JsonUIPrefabBuilder.BuildFromJsonFile(path)))
throw new Exception("Prefab build failed: " + name);
}
return "Updated " + changed + " standard control imports; rebuilt 2 JSON UI prefabs. " + Verify();
}
public static string Verify()
{
int checks = 0, controls = 0, slicedImages = 0, states = 0;
Action<bool, string> check = (ok, message) => { if (!ok) throw new Exception(message); checks++; };
var scene = EditorSceneManager.NewPreviewScene();
try
{
foreach (string guid in AssetDatabase.FindAssets("t:Texture2D", new[] { "Assets/Bundles/UI/Texture/Common" }))
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (!UiSpriteNineSliceRules.IsStandardControl(path)) continue;
controls++;
var importer = (TextureImporter)AssetImporter.GetAtPath(path);
importer.GetSourceTextureWidthAndHeight(out int width, out int height);
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(path);
check(sprite != null && sprite.border == UiSpriteNineSliceRules.GetBorder(path, width, height), "Border mismatch: " + path);
var settings = new TextureImporterSettings(); importer.ReadTextureSettings(settings);
check(settings.spriteMeshType == SpriteMeshType.FullRect, "Not Full Rect: " + path);
check(sprite.border.x > 0 && sprite.border.y > 0 && sprite.border.z > 0 && sprite.border.w > 0, "Empty border: " + path);
check(sprite.border.x + sprite.border.z < sprite.rect.width && sprite.border.y + sprite.border.w < sprite.rect.height, "No stretchable center: " + path);
var probeGo = new GameObject("NineSliceProbe", typeof(RectTransform), typeof(NineSliceProbe));
SceneManager.MoveGameObjectToScene(probeGo, scene);
try
{
var image = probeGo.GetComponent<NineSliceProbe>();
image.sprite = sprite; image.type = Image.Type.Sliced;
foreach (Vector2 size in new[] { sprite.rect.size, sprite.rect.size * 2f, sprite.rect.size * new Vector2(3f, 1.25f) })
{
image.rectTransform.sizeDelta = size;
using (var vh = new VertexHelper())
{
image.Populate(vh);
var vertices = new List<UIVertex>(); vh.GetUIVertexStream(vertices);
var x = vertices.Select(v => v.position.x).Distinct().OrderBy(v => v).ToArray();
var y = vertices.Select(v => v.position.y).Distinct().OrderBy(v => v).ToArray();
check(x.Length == 4 && y.Length == 4, "Not a nine-slice mesh: " + path);
check(Mathf.Abs(x[1] - x[0] - sprite.border.x) < .01f && Mathf.Abs(x[3] - x[2] - sprite.border.z) < .01f
&& Mathf.Abs(y[1] - y[0] - sprite.border.y) < .01f && Mathf.Abs(y[3] - y[2] - sprite.border.w) < .01f,
"Stretched corner borders: " + path);
}
}
}
finally { UnityEngine.Object.DestroyImmediate(probeGo); }
}
check(controls == 26, "Expected 26 standard control sprites, found " + controls);
foreach (string name in Names)
{
string json = Path.Combine(Repo, "Doc/UIPrefabCreater", name + ".json");
check(JsonUIPrefabBuilder.Validate(json).Ok, "Schema validation failed: " + name);
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Bundles/UI/Prefab/" + name + ".prefab");
foreach (var image in prefab.GetComponentsInChildren<Image>(true))
{
if (image.sprite == null || !UiSpriteNineSliceRules.IsStandardControl(AssetDatabase.GetAssetPath(image.sprite))) continue;
slicedImages++;
check(image.type == Image.Type.Sliced && image.hasBorder, "Prefab nine-slice missing: " + name + "/" + image.name);
}
foreach (var button in prefab.GetComponentsInChildren<Button>(true))
foreach (var sprite in new[] { button.spriteState.highlightedSprite, button.spriteState.pressedSprite, button.spriteState.disabledSprite })
{
if (sprite == null || !UiSpriteNineSliceRules.IsStandardControl(AssetDatabase.GetAssetPath(sprite))) continue;
states++;
check(sprite.border.sqrMagnitude > 0, "Button state border missing: " + button.name);
}
}
// A wrong JSON Image type must fail validation instead of producing a distorted prefab.
string invalidPath = Path.Combine(Repo, "Tools/output/ui_nine_slice_invalid.json");
try
{
var doc = JObject.Parse(File.ReadAllText(Path.Combine(Repo, "Doc/UIPrefabCreater/UI_ConfirmDialog.json")));
var comp = doc["nodes"].SelectMany(n => n["components"]).First(c => (string)c["sprite"] == "ui_panel_popup");
comp["imageType"] = "Simple";
File.WriteAllText(invalidPath, doc.ToString());
var validation = JsonUIPrefabBuilder.Validate(invalidPath);
check(!validation.Ok && validation.Errors.Any(e => e.Contains("imageType: Sliced")), "Wrong Image type was accepted.");
}
finally { if (File.Exists(invalidPath)) File.Delete(invalidPath); }
check(UiSpriteNineSliceRules.ApplyExisting() == 0, "Migration is not idempotent.");
}
finally { EditorSceneManager.ClosePreviewScene(scene); }
var result = new JObject { ["controls"] = controls, ["slicedPrefabImages"] = slicedImages, ["buttonStates"] = states, ["checks"] = checks, ["passed"] = true };
File.WriteAllText(Path.Combine(Repo, "Tools/output/ui_nine_slice_verification.json"), result.ToString());
return checks + " checks passed: " + controls + " sprites, " + slicedImages + " sliced prefab images, " + states + " button states; three stretch sizes preserve corner borders.";
}
}
public sealed class NineSliceProbe : Image
{
public void Populate(VertexHelper vh) { base.OnPopulateMesh(vh); }
}