AlicizaX/Client/Packages/com.alicizax.uxtool/Editor/UXGUI/SceneView/UIEditWindow/UXComponentCreateVisualAsset.cs

352 lines
13 KiB
C#
Raw Normal View History

2025-12-01 16:46:28 +08:00
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 Aliciza.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<UXComponentType> 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<UXComponentType>();
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<GameObject>(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<RectTransform>();
if (rect == null)
{
rect = prefabContentsRoot.GetComponentInChildren<RectTransform>();
}
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<Canvas>() ?? prefabContentsRoot.GetComponentInChildren<Canvas>();
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<GraphicRaycaster>();
if (gr != null) Object.DestroyImmediate(gr);
else
{
var grChild = prefabContentsRoot.GetComponentInChildren<GraphicRaycaster>();
if (grChild != null) Object.DestroyImmediate(grChild);
}
var canvas = prefabContentsRoot.GetComponent<Canvas>();
if (canvas != null) Object.DestroyImmediate(canvas);
else
{
var canvasChild = prefabContentsRoot.GetComponentInChildren<Canvas>();
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<GameObject>(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<UXComponentCreateWindow>();
if (wnd != null) wnd.Close();
}
}
catch (Exception ex)
{
Debug.LogError($"创建 prefab 时发生异常: {ex}");
EditorUtility.DisplayDialog("异常", $"创建 prefab 时发生异常,请查看控制台。", "确定");
}
finally
{
// 一定要卸载 prefab contents如果加载成功
if (prefabContentsRoot != null)
PrefabUtility.UnloadPrefabContents(prefabContentsRoot);
}
}
/// <summary>
/// 确保 AssetDatabase 中的文件夹存在(以 "Assets" 为根),会逐级创建缺失的子文件夹。
/// 传入示例: "Assets/MyFolder/SubFolder"
/// </summary>
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;
}
}
}
}