using System; using System.IO; using System.Text.RegularExpressions; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; namespace BaseGames.Editor.Shared { /// /// 资产快速创建向导 —— 弹出式 EditorWindow,引导输入 ID 并预览文件名, /// 一键创建 ScriptableObject 到指定文件夹。 /// 用法: AssetCreationWizard.Show<QuestSO>(folder, prefix, (asset, id) => { asset.questId = id; }); /// public class AssetCreationWizard : EditorWindow { private string _folder; private string _prefix; private string _idInput = ""; private string _typeName; private Type _assetType; private Action _onCreated; private TextField _idField; private Label _previewLabel; // ── 公开入口 ───────────────────────────────────────────────────────── /// /// 打开向导。资产创建完成后以 (asset, id) 形式回调,调用方可在回调中自行设置 ID 字段。 /// public static void Show(string folder, string prefix, Action onCreated) where T : ScriptableObject { string displayName = typeof(T).Name.EndsWith("SO") ? typeof(T).Name[..^2] : typeof(T).Name; var win = CreateInstance(); win.titleContent = new GUIContent($"新建 {displayName}"); win._folder = folder; win._prefix = prefix; win._assetType = typeof(T); win._typeName = displayName; win._onCreated = (so, id) => onCreated((T)so, id); win.minSize = new Vector2(320, 132); win.maxSize = new Vector2(480, 132); win.ShowUtility(); } // ── UI 构建 ────────────────────────────────────────────────────────── private void CreateGUI() { var root = rootVisualElement; root.style.paddingTop = 12; root.style.paddingBottom = 12; root.style.paddingLeft = 16; root.style.paddingRight = 16; // 说明行 var desc = new Label($"将在 {_folder} 中新建一个 {_typeName}"); desc.style.fontSize = 10; desc.style.opacity = 0.55f; desc.style.marginBottom = 10; root.Add(desc); // ID 输入行 var row = new VisualElement(); row.style.flexDirection = FlexDirection.Row; row.style.alignItems = Align.Center; row.style.marginBottom = 6; var idLabel = new Label("ID:"); idLabel.style.width = 36; idLabel.style.unityTextAlign = TextAnchor.MiddleLeft; idLabel.style.flexShrink = 0; row.Add(idLabel); _idField = new TextField { value = "" }; _idField.style.flexGrow = 1; _idField.RegisterValueChangedCallback(evt => { _idInput = evt.newValue; RefreshPreview(); }); row.Add(_idField); root.Add(row); // 文件名预览 _previewLabel = new Label("文件名:(请输入 ID)"); _previewLabel.style.fontSize = 10; _previewLabel.style.opacity = 0.5f; _previewLabel.style.marginBottom = 12; root.Add(_previewLabel); // 按钮行 var btnRow = new VisualElement(); btnRow.style.flexDirection = FlexDirection.Row; btnRow.style.justifyContent = Justify.FlexEnd; var cancelBtn = new Button(Close) { text = "取消" }; cancelBtn.style.width = 56; cancelBtn.style.marginRight = 6; btnRow.Add(cancelBtn); var createBtn = new Button(DoCreate) { text = "创建" }; createBtn.style.width = 56; btnRow.Add(createBtn); root.Add(btnRow); // 自动聚焦 ID 输入框 _idField.schedule.Execute(() => _idField.Focus()).StartingIn(50); } // ── 私有逻辑 ───────────────────────────────────────────────────────── private void RefreshPreview() { if (_previewLabel == null) return; _previewLabel.text = string.IsNullOrWhiteSpace(_idInput) ? "文件名:(请输入 ID)" : $"文件名:{_prefix}{_idInput}.asset"; } private void DoCreate() { if (string.IsNullOrWhiteSpace(_idInput)) { EditorUtility.DisplayDialog("ID 不能为空", "请输入有效的 ID 后再创建。", "确定"); return; } if (!Regex.IsMatch(_idInput, @"^[\w\-]+$")) { EditorUtility.DisplayDialog("ID 格式有误", "ID 只能包含字母、数字、下划线或连字符。", "确定"); return; } if (!Directory.Exists(_folder)) Directory.CreateDirectory(_folder); string path = $"{_folder}/{_prefix}{_idInput}.asset"; if (File.Exists(path)) { EditorUtility.DisplayDialog("文件已存在", $"路径已存在:\n{path}\n请更换 ID。", "确定"); return; } var asset = CreateInstance(_assetType) as ScriptableObject; AssetDatabase.CreateAsset(asset, path); Undo.RegisterCreatedObjectUndo(asset, $"创建 {_typeName} {_idInput}"); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); _onCreated?.Invoke(asset, _idInput); Close(); } } }