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