Merge branch 'main' of gitea.bywaystudios.com:zhanghongbo/Art_SubModule

This commit is contained in:
zhengxianxin 2026-04-02 19:21:59 +08:00
commit a571730e6d
4 changed files with 368 additions and 147 deletions

View File

@ -22,6 +22,7 @@ namespace EditorArt_Tools
private const string SO_ROOT_PATH = "Assets/Art_SubModule/Art_SO";
private const string JSON_ROOT_PATH = "Assets/Art_SubModule/Art_Json";
private const string BYTES_ROOT_PATH = "Assets/Art_SubModule/Art_Bytes";
private const string FOLDER_FOLDOUT_PREFS_KEY_PREFIX = "ArtResourceConfigEditor.FolderFoldout.";
// ===== 数据 =====
private List<ArtTableSO> allTables = new List<ArtTableSO>();
@ -84,6 +85,7 @@ namespace EditorArt_Tools
private void OnEnable()
{
LoadFolderFoldoutStates();
RefreshTableList();
InitializeSpinePreview();
@ -290,16 +292,23 @@ namespace EditorArt_Tools
string folderName = folderGroup.Key;
if (!folderFoldouts.ContainsKey(folderName))
{
folderFoldouts[folderName] = true;
folderFoldouts[folderName] = GetFolderFoldoutState(folderName);
}
// 绘制文件夹
EditorGUILayout.BeginHorizontal();
folderFoldouts[folderName] = EditorGUILayout.Foldout(
folderFoldouts[folderName],
bool previousFoldoutState = folderFoldouts[folderName];
bool newFoldoutState = EditorGUILayout.Foldout(
previousFoldoutState,
$"📁 {folderName} ({folderGroup.Count()})",
true,
EditorStyles.foldoutHeader);
if (newFoldoutState != previousFoldoutState)
{
folderFoldouts[folderName] = newFoldoutState;
SaveFolderFoldoutState(folderName, newFoldoutState);
}
EditorGUILayout.EndHorizontal();
// 绘制该文件夹下的配置表
@ -355,6 +364,26 @@ namespace EditorArt_Tools
return "根目录";
}
private void LoadFolderFoldoutStates()
{
folderFoldouts.Clear();
}
private bool GetFolderFoldoutState(string folderName)
{
return EditorPrefs.GetBool(GetFolderFoldoutPrefsKey(folderName), false);
}
private void SaveFolderFoldoutState(string folderName, bool isExpanded)
{
EditorPrefs.SetBool(GetFolderFoldoutPrefsKey(folderName), isExpanded);
}
private string GetFolderFoldoutPrefsKey(string folderName)
{
return FOLDER_FOLDOUT_PREFS_KEY_PREFIX + folderName;
}
#endregion
#region Item导航
@ -377,10 +406,14 @@ namespace EditorArt_Tools
{
AddNewItem();
}
if (GUILayout.Button("📁 批量导入Sprite", GUILayout.Height(25)))
if (GUILayout.Button("📁 按文件夹导入", GUILayout.Height(25)))
{
BatchImportSprites();
}
if (GUILayout.Button("🖼 批量导入图片", GUILayout.Height(25)))
{
BatchImportSpritesByFiles();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
@ -885,12 +918,25 @@ namespace EditorArt_Tools
// 如果有未保存的更改,提示用户
if (hasUnsavedChanges && selectedTable != null && selectedTable != table)
{
if (!EditorUtility.DisplayDialog("切换配置表",
"当前有未保存的更改,切换将丢失这些更改。\n确定要切换吗",
"切换", "取消"))
// 0=保存并切换, 1=取消, 2=不保存直接切换
int result = EditorUtility.DisplayDialogComplex("未保存的更改",
$"当前配置表「{selectedTable.TableName}」有未保存的更改。",
"保存并切换", "取消", "不保存,直接切换");
if (result == 0)
{
// 保存当前表后再切换
SaveCurrentTable();
// 如果保存失败(仍有未保存标记),中止切换
if (hasUnsavedChanges)
return;
}
else if (result == 1)
{
// 取消切换
return;
}
// result == 2: 不保存,继续切换
}
selectedTable = table;
@ -1240,6 +1286,102 @@ namespace EditorArt_Tools
}
}
private void BatchImportSpritesByFiles()
{
if (selectedTable == null)
{
EditorUtility.DisplayDialog("错误", "请先选择一个配置表", "确定");
return;
}
BatchImportByDragWindow.Show(this);
}
/// <summary>
/// 通过文件路径列表导入Sprite供拖拽导入窗口调用
/// </summary>
public void ImportSpritesByAssetPaths(string[] assetPaths)
{
if (selectedTable == null || assetPaths == null || assetPaths.Length == 0)
return;
var existingSpritePaths = new HashSet<string>(
tempItemsList
.Select(GetSpriteReferencePath)
.Where(path => !string.IsNullOrEmpty(path)));
var spritesToImport = new List<Sprite>();
var spritePathsToImport = new List<string>();
int skippedExistingCount = 0;
int notSpriteCount = 0;
foreach (string assetPath in assetPaths)
{
if (existingSpritePaths.Contains(assetPath))
{
skippedExistingCount++;
continue;
}
// 尝试加载为Sprite
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
if (sprite == null)
{
notSpriteCount++;
continue;
}
spritesToImport.Add(sprite);
spritePathsToImport.Add(assetPath);
existingSpritePaths.Add(assetPath);
}
if (spritesToImport.Count == 0)
{
string msg = "没有可导入的Sprite资源。";
if (notSpriteCount > 0)
msg += $"\n{notSpriteCount} 个文件不是Sprite类型请检查图片导入设置。";
if (skippedExistingCount > 0)
msg += $"\n{skippedExistingCount} 个已存在于当前表格中,已跳过。";
EditorUtility.DisplayDialog("提示", msg, "确定");
return;
}
int startId = tempItemsList.Count > 0 ? tempItemsList.Max(i => i.Id) + 1 : 1;
int importCount = 0;
for (int i = 0; i < spritesToImport.Count; i++)
{
Sprite sprite = spritesToImport[i];
string assetPath = spritePathsToImport[i];
var newItem = new ArtItemData
{
Id = startId + importCount,
Name = sprite.name,
Desc = "",
Sprite = sprite,
SpritePath = assetPath
};
tempItemsList.Add(newItem);
importCount++;
}
if (importCount > 0)
{
hasUnsavedChanges = true;
string msg = $"成功导入 {importCount} 个Sprite资源";
if (skippedExistingCount > 0)
msg += $"\n跳过已存在引用 {skippedExistingCount} 个";
if (notSpriteCount > 0)
msg += $"\n跳过非Sprite文件 {notSpriteCount} 个";
msg += "\n记得点击保存按钮";
EditorUtility.DisplayDialog("导入成功", msg, "确定");
Repaint();
}
}
private void SaveCurrentTable()
{
if (selectedTable == null)
@ -1248,6 +1390,21 @@ namespace EditorArt_Tools
return;
}
selectedTable.TableName = (selectedTable.TableName ?? string.Empty).Trim();
if (string.IsNullOrEmpty(selectedTable.TableName))
{
EditorUtility.DisplayDialog("保存失败", "表格名称不能为空。", "确定");
return;
}
if (selectedTable.TableName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
{
EditorUtility.DisplayDialog("保存失败", "表格名称包含非法文件名字符,请修改后重试。", "确定");
return;
}
string originalSoPath = AssetDatabase.GetAssetPath(selectedTable);
// 检查表格ID和名称的唯一性
foreach (var table in allTables)
{
@ -1323,6 +1480,13 @@ namespace EditorArt_Tools
selectedTable.Items.Add(newItem);
}
selectedTable.name = selectedTable.TableName;
if (!TrySyncTableAssetNames(selectedTable, originalSoPath))
{
return;
}
// 保存SO
EditorUtility.SetDirty(selectedTable);
AssetDatabase.SaveAssets();
@ -1341,10 +1505,53 @@ namespace EditorArt_Tools
hasUnsavedChanges = false;
var savedTable = selectedTable;
RefreshTableList();
SelectTable(savedTable);
Debug.Log($"配置表保存成功: {selectedTable.TableName}");
EditorUtility.DisplayDialog("保存成功", $"配置表「{selectedTable.TableName}」已保存\n共 {selectedTable.Items.Count} 项", "确定");
}
private bool TrySyncTableAssetNames(ArtTableSO table, string originalSoPath)
{
if (table == null || string.IsNullOrEmpty(originalSoPath))
{
return false;
}
string originalAssetName = Path.GetFileNameWithoutExtension(originalSoPath);
if (string.Equals(originalAssetName, table.TableName, System.StringComparison.Ordinal))
{
return true;
}
string originalJsonPath = GetJsonPathFromSoPath(originalSoPath);
string renameError = AssetDatabase.RenameAsset(originalSoPath, table.TableName);
if (!string.IsNullOrEmpty(renameError))
{
EditorUtility.DisplayDialog("保存失败",
$"重命名配置表文件失败:{renameError}",
"确定");
return false;
}
string newSoPath = AssetDatabase.GetAssetPath(table);
string newJsonPath = GetJsonPathFromSoPath(newSoPath);
if (!string.Equals(originalJsonPath, newJsonPath, System.StringComparison.OrdinalIgnoreCase)
&& AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(originalJsonPath) != null)
{
AssetDatabase.DeleteAsset(originalJsonPath);
}
return true;
}
private string GetJsonPathFromSoPath(string soPath)
{
return soPath.Replace(SO_ROOT_PATH, JSON_ROOT_PATH).Replace(".asset", ".json");
}
#endregion
#region JSON同步
@ -2302,4 +2509,158 @@ namespace EditorArt_Tools
#endregion
}
/// <summary>
/// 批量导入图片的拖拽窗口
/// 支持从Project窗口拖拽多个png图片进来
/// </summary>
public class BatchImportByDragWindow : EditorWindow
{
private ArtResourceConfigEditor parentEditor;
private List<UnityEngine.Object> draggedObjects = new List<UnityEngine.Object>();
private Vector2 scrollPos;
public static void Show(ArtResourceConfigEditor editor)
{
var window = GetWindow<BatchImportByDragWindow>("批量导入图片");
window.parentEditor = editor;
window.draggedObjects.Clear();
window.minSize = new Vector2(400, 500);
window.ShowUtility();
}
private void OnGUI()
{
EditorGUILayout.LabelField("批量导入图片", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"从 Project 窗口中将 png 图片拖拽到下方区域,\n支持多选拖入。只有 Sprite 类型的图片会被导入。",
MessageType.Info);
EditorGUILayout.Space(5);
// 拖拽区域
Rect dropArea = GUILayoutUtility.GetRect(0, 100, GUILayout.ExpandWidth(true));
GUI.Box(dropArea, "🖼 将图片拖拽到这里", new GUIStyle(GUI.skin.box)
{
alignment = TextAnchor.MiddleCenter,
fontSize = 16,
fontStyle = FontStyle.Bold
});
HandleDragAndDrop(dropArea);
EditorGUILayout.Space(5);
// 已添加的列表
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"已选择: {draggedObjects.Count} 个文件", EditorStyles.boldLabel);
if (draggedObjects.Count > 0)
{
if (GUILayout.Button("清空", GUILayout.Width(60)))
{
draggedObjects.Clear();
}
}
EditorGUILayout.EndHorizontal();
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
for (int i = draggedObjects.Count - 1; i >= 0; i--)
{
EditorGUILayout.BeginHorizontal("box");
EditorGUILayout.ObjectField(draggedObjects[i], typeof(Texture2D), false);
if (GUILayout.Button("✕", GUILayout.Width(25)))
{
draggedObjects.RemoveAt(i);
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
EditorGUILayout.Space(10);
// 导入按钮
EditorGUI.BeginDisabledGroup(draggedObjects.Count == 0 || parentEditor == null);
GUI.backgroundColor = new Color(0.3f, 0.8f, 0.3f);
if (GUILayout.Button($"确认导入 ({draggedObjects.Count} 个)", GUILayout.Height(35)))
{
DoImport();
}
GUI.backgroundColor = Color.white;
EditorGUI.EndDisabledGroup();
}
private void HandleDragAndDrop(Rect dropArea)
{
Event evt = Event.current;
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
if (!dropArea.Contains(evt.mousePosition))
return;
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
var existingPaths = new HashSet<string>(
draggedObjects.Select(o => AssetDatabase.GetAssetPath(o)));
foreach (var obj in DragAndDrop.objectReferences)
{
string path = AssetDatabase.GetAssetPath(obj);
if (string.IsNullOrEmpty(path))
continue;
// 如果拖入的是文件夹递归查找png
if (AssetDatabase.IsValidFolder(path))
{
string[] guids = AssetDatabase.FindAssets("t:Texture2D", new[] { path });
foreach (string guid in guids)
{
string texPath = AssetDatabase.GUIDToAssetPath(guid);
if (!texPath.EndsWith(".png", System.StringComparison.OrdinalIgnoreCase))
continue;
if (existingPaths.Contains(texPath))
continue;
var texObj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(texPath);
if (texObj != null)
{
draggedObjects.Add(texObj);
existingPaths.Add(texPath);
}
}
}
else
{
// 只接受png文件
if (!path.EndsWith(".png", System.StringComparison.OrdinalIgnoreCase))
continue;
if (existingPaths.Contains(path))
continue;
draggedObjects.Add(obj);
existingPaths.Add(path);
}
}
}
evt.Use();
break;
}
}
private void DoImport()
{
var assetPaths = draggedObjects
.Select(o => AssetDatabase.GetAssetPath(o))
.Where(p => !string.IsNullOrEmpty(p))
.ToArray();
parentEditor.ImportSpritesByAssetPaths(assetPaths);
Close();
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

View File

@ -1,140 +0,0 @@
fileFormatVersion: 2
guid: 538da807bccdbfe4a927761b4bc37384
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: 52
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 1
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant: