using System; using System.IO; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.UI; using UnityEngine.UIElements; using Button = UnityEngine.UIElements.Button; using Object = UnityEngine.Object; namespace AlicizaX.UXTool { public enum UXComponentType { Window, Widget, Base } public partial class UXCreateWindowVisualAsset : VisualElement { // 这是你原先的基础 prefab(作为模板) private const string PrefabPath = "Packages/com.alicizax.uxtool/Editor/UXGUI/Res/Component/View.prefab"; private readonly PopupField createTypePopupField; private readonly Button btnConfirm; private readonly TextField resTextField; private readonly Vector2IntField resVectorField; private readonly Label pathLabel; private Action _createCallBack; private string _folderPath; public void SetCallBack(string path, Action callback) { _createCallBack = callback; _folderPath = path; UpdatePathLabel(); } public UXCreateWindowVisualAsset() { style.flexGrow = 1; style.flexShrink = 0; style.height = Length.Percent(100); style.width = Length.Percent(100); UXComponentType[] createTypes = new[] { UXComponentType.Window, UXComponentType.Widget, UXComponentType.Base }; // 组件名称 resTextField = new TextField(); resTextField.label = "组件名称"; resTextField.style.unityFontStyleAndWeight = FontStyle.Bold; resTextField.style.paddingBottom = 5; resTextField.RegisterValueChangedCallback(evt => UpdatePathLabel()); // 类型下拉 createTypePopupField = new PopupField(); createTypePopupField.name = "createTypePopupField"; createTypePopupField.label = "组件类型"; createTypePopupField.choices = createTypes.ToList(); createTypePopupField.style.unityFontStyleAndWeight = FontStyle.Bold; createTypePopupField.index = 0; createTypePopupField.style.paddingBottom = 5; createTypePopupField.RegisterValueChangedCallback(evt => { UpdateResolutionVisibility(evt.newValue); UpdatePathLabel(); }); resVectorField = new Vector2IntField(); resVectorField.label = "组件大小"; resVectorField.value = new Vector2Int(800, 600); resVectorField.style.paddingBottom = 5; resVectorField.style.unityFontStyleAndWeight = FontStyle.Bold; resVectorField.RegisterValueChangedCallback(evt => { // 强制为非负值(如果需要可以改为 >=1) var v = evt.newValue; v.x = Mathf.Max(0, v.x); v.y = Mathf.Max(0, v.y); // 如果用户输入负数,修正回去并刷新字段 if (v != evt.newValue) resVectorField.SetValueWithoutNotify(v); UpdatePathLabel(); }); // 路径显示 pathLabel = new Label(); pathLabel.style.unityFontStyleAndWeight = FontStyle.Bold; pathLabel.style.marginTop = 6; pathLabel.text = GetFullPrefabPathText(); // 创建按钮 btnConfirm = new Button(OnBtnConfirmClick); btnConfirm.style.height = new Length(30, LengthUnit.Pixel); btnConfirm.text = "创建"; btnConfirm.style.unityFontStyleAndWeight = FontStyle.Bold; // Add in desired order Add(resTextField); Add(createTypePopupField); Add(resVectorField); Add(pathLabel); Add(btnConfirm); UpdateResolutionVisibility(createTypePopupField.value); } private void UpdateResolutionVisibility(UXComponentType type) { if (type == UXComponentType.Window) { resVectorField.style.display = DisplayStyle.None; } else { resVectorField.style.display = DisplayStyle.Flex; } } private string GetFullPrefabPathText() { var name = resTextField != null ? resTextField.value?.Trim() : ""; var folder = _folderPath ?? ""; if (string.IsNullOrEmpty(folder)) return "<路径未设置>"; if (string.IsNullOrEmpty(name)) return folder; // ensure folder uses forward slashes var f = folder.Replace("\\", "/").TrimEnd('/'); return $"{f}/{name}.prefab"; } private void UpdatePathLabel() { if (pathLabel != null) pathLabel.text = GetFullPrefabPathText(); } private void OnBtnConfirmClick() { // 基本校验 string componentName = resTextField.value?.Trim(); if (string.IsNullOrEmpty(componentName)) { EditorUtility.DisplayDialog("错误", "请输入组件名称。", "确定"); return; } UXComponentType type = createTypePopupField.value; Vector2Int res = resVectorField.value; int width = res.x; int height = res.y; if (type != UXComponentType.Window) { if (width <= 0 || height <= 0) { EditorUtility.DisplayDialog("错误", "请设置有效的分辨率(宽度和高度必须为正整数)。", "确定"); return; } } if (string.IsNullOrEmpty(_folderPath)) { EditorUtility.DisplayDialog("错误", "_folderPath 未设置,请先设置保存目录。", "确定"); return; } // 确保文件夹存在(基于 AssetDatabase) string folder = _folderPath.Replace("\\", "/").TrimEnd('/'); EnsureFolderExists(folder); // 先确认模板 prefab 可以加载 var templatePrefab = AssetDatabase.LoadAssetAtPath(PrefabPath); if (templatePrefab == null) { EditorUtility.DisplayDialog("错误", $"无法加载基础 prefab:{PrefabPath}", "确定"); return; } // 使用 PrefabUtility.LoadPrefabContents 在 prefab asset 上直接编辑(不会在场景产生临时实例) GameObject prefabContentsRoot = null; try { prefabContentsRoot = PrefabUtility.LoadPrefabContents(PrefabPath); if (prefabContentsRoot == null) { EditorUtility.DisplayDialog("错误", $"无法通过 LoadPrefabContents 加载 prefab: {PrefabPath}", "确定"); return; } // 修改 prefabContentsRoot(模板的根) prefabContentsRoot.name = componentName; // 找到根或第一个 RectTransform(通常模板根是 RectTransform) RectTransform rect = prefabContentsRoot.GetComponent(); if (rect == null) { rect = prefabContentsRoot.GetComponentInChildren(); } if (rect == null) { EditorUtility.DisplayDialog("错误", "Prefab 模板中未找到 RectTransform,无法设置尺寸。", "确定"); return; } // 根据类型调整 if (type == UXComponentType.Window) { // 全屏拉伸 rect.localScale = Vector3.one; rect.localPosition = Vector3.zero; rect.pivot = new Vector2(0.5f, 0.5f); rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; rect.anchoredPosition = Vector2.zero; // 如果存在 Canvas,确保 RenderMode 为 ScreenSpaceOverlay 并且 localScale 为 1 var canvas = prefabContentsRoot.GetComponent() ?? prefabContentsRoot.GetComponentInChildren(); if (canvas != null) { canvas.renderMode = RenderMode.ScreenSpaceOverlay; #if UNITY_2019_1_OR_NEWER // worldCamera 设为 null 以避免意外引用 canvas.worldCamera = null; #endif } } else { if (type == UXComponentType.Base) { // 移除 GraphicRaycaster 与 Canvas(如果存在) var gr = prefabContentsRoot.GetComponent(); if (gr != null) Object.DestroyImmediate(gr); else { var grChild = prefabContentsRoot.GetComponentInChildren(); if (grChild != null) Object.DestroyImmediate(grChild); } var canvas = prefabContentsRoot.GetComponent(); if (canvas != null) Object.DestroyImmediate(canvas); else { var canvasChild = prefabContentsRoot.GetComponentInChildren(); if (canvasChild != null) Object.DestroyImmediate(canvasChild); } } rect.localScale = Vector3.one; rect.pivot = new Vector2(0.5f, 0.5f); rect.anchorMin = rect.anchorMax = new Vector2(0.5f, 0.5f); rect.anchoredPosition = Vector2.zero; rect.sizeDelta = new Vector2(width, height); } string prefabPath = $"{folder}/{componentName}.prefab"; prefabPath = prefabPath.Replace("\\", "/"); // 如果已存在,询问是否覆盖 var existing = AssetDatabase.LoadAssetAtPath(prefabPath); if (existing != null) { bool overwrite = EditorUtility.DisplayDialog("覆盖确认", $"已存在 {componentName}.prefab,是否覆盖?", "覆盖", "取消"); if (!overwrite) { // 取消:卸载 prefabContents,并返回 return; } } // 保存为新的 prefab asset(将 prefabContentsRoot 内容保存到目标 prefabPath) GameObject saved = PrefabUtility.SaveAsPrefabAsset(prefabContentsRoot, prefabPath); if (saved == null) { EditorUtility.DisplayDialog("失败", $"保存 prefab 失败:{prefabPath}", "确定"); } else { AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); _createCallBack?.Invoke(); // 尝试关闭创建窗口(如果存在) var wnd = EditorWindow.GetWindow(); if (wnd != null) wnd.Close(); } } catch (Exception ex) { Debug.LogError($"创建 prefab 时发生异常: {ex}"); EditorUtility.DisplayDialog("异常", $"创建 prefab 时发生异常,请查看控制台。", "确定"); } finally { // 一定要卸载 prefab contents(如果加载成功) if (prefabContentsRoot != null) PrefabUtility.UnloadPrefabContents(prefabContentsRoot); } } /// /// 确保 AssetDatabase 中的文件夹存在(以 "Assets" 为根),会逐级创建缺失的子文件夹。 /// 传入示例: "Assets/MyFolder/SubFolder" /// private void EnsureFolderExists(string folderPath) { if (string.IsNullOrEmpty(folderPath)) throw new ArgumentNullException(nameof(folderPath)); var path = folderPath.Replace("\\", "/").TrimEnd('/'); if (AssetDatabase.IsValidFolder(path)) return; string[] parts = path.Split('/'); if (parts.Length == 0 || parts[0] != "Assets") { Debug.LogError("folderPath 必须以 'Assets' 开头。"); return; } string cur = "Assets"; for (int i = 1; i < parts.Length; i++) { string next = cur + "/" + parts[i]; if (!AssetDatabase.IsValidFolder(next)) { AssetDatabase.CreateFolder(cur, parts[i]); } cur = next; } } } }