352 lines
13 KiB
C#
352 lines
13 KiB
C#
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<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;
|
||
}
|
||
}
|
||
}
|
||
}
|