AlicizaX/Client/Packages/com.alicizax.uxtool/Editor/UXGUI/SceneView/UIEditWindow/UXComponentCreateVisualAsset.cs
陈思海 5862fb2af1 add
1.新增手柄相关适配
2.新增设备重映射绑定
3.新增设备映射图标更新
2025-12-09 20:31:44 +08:00

352 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}
}