feat: Enhance Addressable tools with improved scanning and filtering features
- Updated AddressReferenceGraphWindow to scan for AddressKeys in the _Game directory and added a warning for missing directories. - Enhanced AddressableBatchTool with new filters for asset types (Prefab, Scene, ScriptableObject, Texture, Audio) and improved UI layout for better usability. - Introduced automatic application of grouping and labeling rules during registration in AddressableBatchTool. - Added functionality to quickly scan the _Game folder and improved address building logic. - Updated AddressableRuleSyncWindow to include handling for custom labels and improved reporting of issues. - Enhanced AddressableRules with a whitelist for known labels and refined grouping and labeling logic based on asset prefixes.
This commit is contained in:
@@ -200,11 +200,15 @@ namespace BaseGames.Editor
|
|||||||
foreach (var kv in keyDict)
|
foreach (var kv in keyDict)
|
||||||
kv.Value.ExistsInAddressables = registeredAddressValues.Contains(kv.Value.Value);
|
kv.Value.ExistsInAddressables = registeredAddressValues.Contains(kv.Value.Value);
|
||||||
|
|
||||||
// 3. 扫描 .cs 文件引用
|
// 3. 扫描 _Game/ 下所有 .cs 文件中对 AddressKeys 的引用
|
||||||
var csFiles = Directory.GetFiles(
|
string gameDir = Path.Combine(Application.dataPath, "_Game");
|
||||||
Path.Combine(Application.dataPath, "Scripts"),
|
if (!Directory.Exists(gameDir))
|
||||||
"*.cs",
|
{
|
||||||
SearchOption.AllDirectories);
|
Debug.LogWarning($"[AddressReferenceGraph] 未找到游戏目录:{gameDir}");
|
||||||
|
foreach (var kv in keyDict) _entries.Add(kv.Value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var csFiles = Directory.GetFiles(gameDir, "*.cs", SearchOption.AllDirectories);
|
||||||
|
|
||||||
foreach (var file in csFiles)
|
foreach (var file in csFiles)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ namespace BaseGames.Editor
|
|||||||
// Tab ③
|
// Tab ③
|
||||||
private List<SelectionEntry> _selectionEntries;
|
private List<SelectionEntry> _selectionEntries;
|
||||||
private Vector2 _selectionScrollPos;
|
private Vector2 _selectionScrollPos;
|
||||||
|
private bool _selFilterPrefab = true;
|
||||||
|
private bool _selFilterScene = true;
|
||||||
|
private bool _selFilterSO = true;
|
||||||
|
private bool _selFilterTexture;
|
||||||
|
private bool _selFilterAudio;
|
||||||
|
|
||||||
// 共用
|
// 共用
|
||||||
private int _targetGroupIndex;
|
private int _targetGroupIndex;
|
||||||
@@ -67,6 +72,7 @@ namespace BaseGames.Editor
|
|||||||
private string _newGroupName = "New Group";
|
private string _newGroupName = "New Group";
|
||||||
private string _newLabel = "";
|
private string _newLabel = "";
|
||||||
private bool _overwriteAddress;
|
private bool _overwriteAddress;
|
||||||
|
private bool _applyRulesOnRegister = true;
|
||||||
|
|
||||||
// ── 样式(惰性初始化)────────────────────────────────────────────────
|
// ── 样式(惰性初始化)────────────────────────────────────────────────
|
||||||
private GUIStyle _headerStyle;
|
private GUIStyle _headerStyle;
|
||||||
@@ -81,7 +87,7 @@ namespace BaseGames.Editor
|
|||||||
public static void OpenWindow()
|
public static void OpenWindow()
|
||||||
{
|
{
|
||||||
var win = GetWindow<AddressableBatchTool>(Title);
|
var win = GetWindow<AddressableBatchTool>(Title);
|
||||||
win.minSize = new Vector2(600, 460);
|
win.minSize = new Vector2(920, 520);
|
||||||
win.Show();
|
win.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,9 +150,11 @@ namespace BaseGames.Editor
|
|||||||
// 列表表头
|
// 列表表头
|
||||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("常量名", _boldStyle, GUILayout.Width(200));
|
EditorGUILayout.LabelField("常量名", _boldStyle, GUILayout.Width(180));
|
||||||
EditorGUILayout.LabelField("地址 Key", _boldStyle, GUILayout.Width(180));
|
EditorGUILayout.LabelField("地址 Key", _boldStyle, GUILayout.Width(160));
|
||||||
EditorGUILayout.LabelField("状态", _boldStyle, GUILayout.Width(100));
|
EditorGUILayout.LabelField("期望分组", _boldStyle, GUILayout.Width(110));
|
||||||
|
EditorGUILayout.LabelField("期望标签", _boldStyle, GUILayout.Width(140));
|
||||||
|
EditorGUILayout.LabelField("状态", _boldStyle, GUILayout.Width(80));
|
||||||
EditorGUILayout.LabelField("匹配资产", _boldStyle);
|
EditorGUILayout.LabelField("匹配资产", _boldStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,24 +168,30 @@ namespace BaseGames.Editor
|
|||||||
{
|
{
|
||||||
using (new EditorGUILayout.HorizontalScope(GUILayout.Height(20)))
|
using (new EditorGUILayout.HorizontalScope(GUILayout.Height(20)))
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField(entry.FieldName, GUILayout.Width(200));
|
EditorGUILayout.LabelField(entry.FieldName, GUILayout.Width(180));
|
||||||
EditorGUILayout.LabelField(entry.AddressKey, GUILayout.Width(180));
|
EditorGUILayout.LabelField(entry.AddressKey, GUILayout.Width(160));
|
||||||
|
EditorGUILayout.LabelField(
|
||||||
|
AddressableRules.GetExpectedGroup(entry.AddressKey) ?? "Default",
|
||||||
|
GUILayout.Width(110));
|
||||||
|
EditorGUILayout.LabelField(
|
||||||
|
FormatLabels(AddressableRules.GetExpectedLabels(entry.AddressKey)),
|
||||||
|
GUILayout.Width(140));
|
||||||
|
|
||||||
if (entry.IsRegistered)
|
if (entry.IsRegistered)
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("✅ 已注册", _okStyle, GUILayout.Width(100));
|
EditorGUILayout.LabelField("✅ 已注册", _okStyle, GUILayout.Width(80));
|
||||||
EditorGUILayout.LabelField(entry.ExistingAssetPath ?? "—");
|
EditorGUILayout.LabelField(entry.ExistingAssetPath ?? "—");
|
||||||
}
|
}
|
||||||
else if (entry.FoundAssetPath != null)
|
else if (entry.FoundAssetPath != null)
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("⚠ 未注册", _warnStyle, GUILayout.Width(100));
|
EditorGUILayout.LabelField("⚠ 未注册", _warnStyle, GUILayout.Width(80));
|
||||||
EditorGUILayout.LabelField(entry.FoundAssetPath, GUILayout.ExpandWidth(true));
|
EditorGUILayout.LabelField(entry.FoundAssetPath, GUILayout.ExpandWidth(true));
|
||||||
if (GUILayout.Button("注册", GUILayout.Width(50)))
|
if (GUILayout.Button("注册", GUILayout.Width(50)))
|
||||||
RegisterKeyEntry(entry);
|
RegisterKeyEntry(entry);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("❌ 未找到", _warnStyle, GUILayout.Width(100));
|
EditorGUILayout.LabelField("❌ 未找到", _warnStyle, GUILayout.Width(80));
|
||||||
entry.ManualAsset = (UnityEngine.Object)EditorGUILayout.ObjectField(
|
entry.ManualAsset = (UnityEngine.Object)EditorGUILayout.ObjectField(
|
||||||
entry.ManualAsset, typeof(UnityEngine.Object), false);
|
entry.ManualAsset, typeof(UnityEngine.Object), false);
|
||||||
if (entry.ManualAsset != null)
|
if (entry.ManualAsset != null)
|
||||||
@@ -247,6 +261,8 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
using (new EditorGUILayout.HorizontalScope())
|
using (new EditorGUILayout.HorizontalScope())
|
||||||
{
|
{
|
||||||
|
if (GUILayout.Button("⚡ 全量扫描 _Game/", GUILayout.Width(150)))
|
||||||
|
QuickScanGameFolder();
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
GUI.enabled = !string.IsNullOrEmpty(_folderPath);
|
GUI.enabled = !string.IsNullOrEmpty(_folderPath);
|
||||||
if (GUILayout.Button("扫描文件夹", GUILayout.Width(100)))
|
if (GUILayout.Button("扫描文件夹", GUILayout.Width(100)))
|
||||||
@@ -266,9 +282,14 @@ namespace BaseGames.Editor
|
|||||||
EditorGUILayout.Space(4);
|
EditorGUILayout.Space(4);
|
||||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("资产路径", _boldStyle, GUILayout.ExpandWidth(true));
|
EditorGUILayout.LabelField("资产路径", _boldStyle, GUILayout.Width(180));
|
||||||
EditorGUILayout.LabelField("预计地址", _boldStyle, GUILayout.Width(200));
|
EditorGUILayout.LabelField("地址", _boldStyle, GUILayout.Width(150));
|
||||||
EditorGUILayout.LabelField("状态", _boldStyle, GUILayout.Width(80));
|
if (_applyRulesOnRegister)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("分组(规则)", _boldStyle, GUILayout.Width(120));
|
||||||
|
EditorGUILayout.LabelField("标签(规则)", _boldStyle, GUILayout.Width(150));
|
||||||
|
}
|
||||||
|
EditorGUILayout.LabelField("状态", _boldStyle, GUILayout.Width(70));
|
||||||
}
|
}
|
||||||
|
|
||||||
_folderScrollPos = EditorGUILayout.BeginScrollView(_folderScrollPos, GUILayout.ExpandHeight(true));
|
_folderScrollPos = EditorGUILayout.BeginScrollView(_folderScrollPos, GUILayout.ExpandHeight(true));
|
||||||
@@ -276,11 +297,16 @@ namespace BaseGames.Editor
|
|||||||
{
|
{
|
||||||
using (new EditorGUILayout.HorizontalScope(GUILayout.Height(18)))
|
using (new EditorGUILayout.HorizontalScope(GUILayout.Height(18)))
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField(entry.AssetPath, GUILayout.ExpandWidth(true));
|
EditorGUILayout.LabelField(entry.AssetPath, GUILayout.Width(180));
|
||||||
entry.Address = EditorGUILayout.TextField(entry.Address, GUILayout.Width(200));
|
entry.Address = EditorGUILayout.TextField(entry.Address, GUILayout.Width(150));
|
||||||
|
if (_applyRulesOnRegister)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(entry.PredictedGroup ?? "Default", GUILayout.Width(120));
|
||||||
|
EditorGUILayout.LabelField(entry.PredictedLabels ?? "—", GUILayout.Width(150));
|
||||||
|
}
|
||||||
var label = entry.AlreadyRegistered ? "✅ 已有" : "待注册";
|
var label = entry.AlreadyRegistered ? "✅ 已有" : "待注册";
|
||||||
var style = entry.AlreadyRegistered ? _okStyle : EditorStyles.miniLabel;
|
var style = entry.AlreadyRegistered ? _okStyle : EditorStyles.miniLabel;
|
||||||
EditorGUILayout.LabelField(label, style, GUILayout.Width(80));
|
EditorGUILayout.LabelField(label, style, GUILayout.Width(70));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EditorGUILayout.EndScrollView();
|
EditorGUILayout.EndScrollView();
|
||||||
@@ -296,6 +322,18 @@ namespace BaseGames.Editor
|
|||||||
EditorGUILayout.LabelField("在 Project 窗口中选中资产或文件夹,然后点击「读取选中项」。", EditorStyles.wordWrappedMiniLabel);
|
EditorGUILayout.LabelField("在 Project 窗口中选中资产或文件夹,然后点击「读取选中项」。", EditorStyles.wordWrappedMiniLabel);
|
||||||
EditorGUILayout.Space(4);
|
EditorGUILayout.Space(4);
|
||||||
|
|
||||||
|
// 资产类型筛选(与 Tab ② 一致,防止误注册不该 Addressable 的文件类型)
|
||||||
|
EditorGUILayout.LabelField("资产类型筛选", EditorStyles.boldLabel);
|
||||||
|
using (new EditorGUILayout.HorizontalScope())
|
||||||
|
{
|
||||||
|
_selFilterPrefab = GUILayout.Toggle(_selFilterPrefab, "Prefab", GUILayout.Width(70));
|
||||||
|
_selFilterScene = GUILayout.Toggle(_selFilterScene, "Scene", GUILayout.Width(70));
|
||||||
|
_selFilterSO = GUILayout.Toggle(_selFilterSO, "SO/Asset", GUILayout.Width(80));
|
||||||
|
_selFilterTexture = GUILayout.Toggle(_selFilterTexture, "Texture", GUILayout.Width(70));
|
||||||
|
_selFilterAudio = GUILayout.Toggle(_selFilterAudio, "Audio", GUILayout.Width(70));
|
||||||
|
}
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
|
||||||
using (new EditorGUILayout.HorizontalScope())
|
using (new EditorGUILayout.HorizontalScope())
|
||||||
{
|
{
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
@@ -316,9 +354,14 @@ namespace BaseGames.Editor
|
|||||||
EditorGUILayout.Space(4);
|
EditorGUILayout.Space(4);
|
||||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("资产路径", _boldStyle, GUILayout.ExpandWidth(true));
|
EditorGUILayout.LabelField("资产路径", _boldStyle, GUILayout.Width(180));
|
||||||
EditorGUILayout.LabelField("注册地址", _boldStyle, GUILayout.Width(200));
|
EditorGUILayout.LabelField("注册地址", _boldStyle, GUILayout.Width(150));
|
||||||
EditorGUILayout.LabelField("状态", _boldStyle, GUILayout.Width(80));
|
if (_applyRulesOnRegister)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("分组(规则)", _boldStyle, GUILayout.Width(120));
|
||||||
|
EditorGUILayout.LabelField("标签(规则)", _boldStyle, GUILayout.Width(150));
|
||||||
|
}
|
||||||
|
EditorGUILayout.LabelField("状态", _boldStyle, GUILayout.Width(70));
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectionScrollPos = EditorGUILayout.BeginScrollView(_selectionScrollPos, GUILayout.ExpandHeight(true));
|
_selectionScrollPos = EditorGUILayout.BeginScrollView(_selectionScrollPos, GUILayout.ExpandHeight(true));
|
||||||
@@ -326,11 +369,16 @@ namespace BaseGames.Editor
|
|||||||
{
|
{
|
||||||
using (new EditorGUILayout.HorizontalScope(GUILayout.Height(18)))
|
using (new EditorGUILayout.HorizontalScope(GUILayout.Height(18)))
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField(entry.AssetPath, GUILayout.ExpandWidth(true));
|
EditorGUILayout.LabelField(entry.AssetPath, GUILayout.Width(180));
|
||||||
entry.Address = EditorGUILayout.TextField(entry.Address, GUILayout.Width(200));
|
entry.Address = EditorGUILayout.TextField(entry.Address, GUILayout.Width(150));
|
||||||
|
if (_applyRulesOnRegister)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(entry.PredictedGroup ?? "Default", GUILayout.Width(120));
|
||||||
|
EditorGUILayout.LabelField(entry.PredictedLabels ?? "—", GUILayout.Width(150));
|
||||||
|
}
|
||||||
var label = entry.AlreadyRegistered ? "✅ 已有" : "待注册";
|
var label = entry.AlreadyRegistered ? "✅ 已有" : "待注册";
|
||||||
var style = entry.AlreadyRegistered ? _okStyle : EditorStyles.miniLabel;
|
var style = entry.AlreadyRegistered ? _okStyle : EditorStyles.miniLabel;
|
||||||
EditorGUILayout.LabelField(label, style, GUILayout.Width(80));
|
EditorGUILayout.LabelField(label, style, GUILayout.Width(70));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EditorGUILayout.EndScrollView();
|
EditorGUILayout.EndScrollView();
|
||||||
@@ -347,22 +395,32 @@ namespace BaseGames.Editor
|
|||||||
using (new EditorGUILayout.HorizontalScope())
|
using (new EditorGUILayout.HorizontalScope())
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("目标分组", GUILayout.Width(70));
|
EditorGUILayout.LabelField("目标分组", GUILayout.Width(70));
|
||||||
if (_groupNames != null && _groupNames.Length > 0)
|
// 自动规则模式下,目标分组由规则决定,手动选择无效
|
||||||
|
GUI.enabled = !_applyRulesOnRegister;
|
||||||
|
if (_applyRulesOnRegister)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("(由 AddressableRules 自动决定)",
|
||||||
|
EditorStyles.miniLabel, GUILayout.Width(200));
|
||||||
|
}
|
||||||
|
else if (_groupNames != null && _groupNames.Length > 0)
|
||||||
{
|
{
|
||||||
_targetGroupIndex = EditorGUILayout.Popup(_targetGroupIndex,
|
_targetGroupIndex = EditorGUILayout.Popup(_targetGroupIndex,
|
||||||
_groupNames, GUILayout.Width(200));
|
_groupNames, GUILayout.Width(200));
|
||||||
}
|
}
|
||||||
|
GUI.enabled = true;
|
||||||
|
|
||||||
EditorGUILayout.Space(8);
|
EditorGUILayout.Space(8);
|
||||||
EditorGUILayout.LabelField("标签", GUILayout.Width(30));
|
EditorGUILayout.LabelField("附加标签", GUILayout.Width(52));
|
||||||
_newLabel = EditorGUILayout.TextField(_newLabel, GUILayout.Width(120));
|
_newLabel = EditorGUILayout.TextField(_newLabel, GUILayout.Width(120));
|
||||||
GUILayout.Label("(留空则不添加标签)", EditorStyles.miniLabel);
|
GUILayout.Label("(可在规则标签基础上追加)", EditorStyles.miniLabel);
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
}
|
}
|
||||||
|
|
||||||
using (new EditorGUILayout.HorizontalScope())
|
using (new EditorGUILayout.HorizontalScope())
|
||||||
{
|
{
|
||||||
_overwriteAddress = GUILayout.Toggle(_overwriteAddress, "已注册的资产也覆盖地址");
|
_overwriteAddress = GUILayout.Toggle(_overwriteAddress, "已注册的资产也覆盖地址");
|
||||||
|
GUILayout.Space(16);
|
||||||
|
_applyRulesOnRegister = GUILayout.Toggle(_applyRulesOnRegister, "自动应用分组/标签规则");
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
if (GUILayout.Button("新建分组…", GUILayout.Width(100)))
|
if (GUILayout.Button("新建分组…", GUILayout.Width(100)))
|
||||||
ShowCreateGroupDialog();
|
ShowCreateGroupDialog();
|
||||||
@@ -479,23 +537,45 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
string absFolder = Path.GetFullPath(_folderPath);
|
string absFolder = Path.GetFullPath(_folderPath);
|
||||||
|
|
||||||
|
// 收集所有文件
|
||||||
|
var allFiles = new List<string>();
|
||||||
foreach (string filter in filters)
|
foreach (string filter in filters)
|
||||||
|
allFiles.AddRange(Directory.GetFiles(absFolder, filter, option));
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (string absPath in Directory.GetFiles(absFolder, filter, option))
|
for (int i = 0; i < allFiles.Count; i++)
|
||||||
{
|
{
|
||||||
|
string absPath = allFiles[i];
|
||||||
|
|
||||||
|
if (i % 20 == 0)
|
||||||
|
EditorUtility.DisplayProgressBar("扫描文件夹",
|
||||||
|
Path.GetFileName(absPath),
|
||||||
|
(float)i / allFiles.Count);
|
||||||
|
|
||||||
string relPath = "Assets" + absPath.Substring(Application.dataPath.Length).Replace('\\', '/');
|
string relPath = "Assets" + absPath.Substring(Application.dataPath.Length).Replace('\\', '/');
|
||||||
|
if (!IsAddressableAssetPath(relPath)) continue;
|
||||||
|
if (ShouldExclude(relPath)) continue;
|
||||||
|
|
||||||
string guid = AssetDatabase.AssetPathToGUID(relPath);
|
string guid = AssetDatabase.AssetPathToGUID(relPath);
|
||||||
if (string.IsNullOrEmpty(guid)) continue;
|
if (string.IsNullOrEmpty(guid)) continue;
|
||||||
|
|
||||||
|
string addr = BuildAddress(relPath);
|
||||||
_folderEntries.Add(new FolderEntry
|
_folderEntries.Add(new FolderEntry
|
||||||
{
|
{
|
||||||
AssetPath = relPath,
|
AssetPath = relPath,
|
||||||
Guid = guid,
|
Guid = guid,
|
||||||
Address = BuildAddress(relPath),
|
Address = addr,
|
||||||
AlreadyRegistered = registeredGuids.Contains(guid),
|
AlreadyRegistered = registeredGuids.Contains(guid),
|
||||||
|
PredictedGroup = AddressableRules.GetExpectedGroup(addr),
|
||||||
|
PredictedLabels = FormatLabels(AddressableRules.GetExpectedLabels(addr)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EditorUtility.ClearProgressBar();
|
||||||
|
}
|
||||||
|
|
||||||
// 去重(多个 filter 可能匹配同一文件)
|
// 去重(多个 filter 可能匹配同一文件)
|
||||||
_folderEntries = _folderEntries
|
_folderEntries = _folderEntries
|
||||||
@@ -504,6 +584,34 @@ namespace BaseGames.Editor
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 返回 true 表示该文件应从 Addressable 注册中排除。
|
||||||
|
/// 规范来源:AddressablesLabelSpec §5.2(禁止注册项)。
|
||||||
|
/// </summary>
|
||||||
|
private static bool ShouldExclude(string relPath)
|
||||||
|
{
|
||||||
|
string lowerPath = relPath.Replace('\\', '/').ToLowerInvariant();
|
||||||
|
string fileName = Path.GetFileNameWithoutExtension(relPath);
|
||||||
|
|
||||||
|
// 测试场景(放在 Scenes/Testings/ 下)
|
||||||
|
if (lowerPath.Contains("/scenes/testings/")) return true;
|
||||||
|
|
||||||
|
// 事件频道 ScriptableObject(EVT_ 前缀)
|
||||||
|
if (fileName.StartsWith("EVT_", StringComparison.Ordinal)) return true;
|
||||||
|
|
||||||
|
// Sprite Atlas — 随依赖它的 Prefab 隐式打包,不单独注册
|
||||||
|
if (Path.GetExtension(relPath) == ".spriteatlas") return true;
|
||||||
|
|
||||||
|
// Material — 随 Prefab 依赖关系打包,不单独注册
|
||||||
|
if (Path.GetExtension(relPath) == ".mat") return true;
|
||||||
|
|
||||||
|
// HitBox / HurtBox 等碰撞盒子 Prefab(子 Prefab,不独立寻址)
|
||||||
|
if (fileName.StartsWith("HitBox_", StringComparison.OrdinalIgnoreCase)) return true;
|
||||||
|
if (fileName.StartsWith("HurtBox_", StringComparison.OrdinalIgnoreCase)) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void RegisterAllFolderEntries()
|
private void RegisterAllFolderEntries()
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -554,12 +662,29 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
private void AddSelectionEntry(string guid, string path, HashSet<string> registeredGuids)
|
private void AddSelectionEntry(string guid, string path, HashSet<string> registeredGuids)
|
||||||
{
|
{
|
||||||
|
if (!IsAddressableAssetPath(path)) return;
|
||||||
|
if (ShouldExclude(path)) return;
|
||||||
|
|
||||||
|
// 资产类型筛选(与 Tab ③ 筛选 Toggle 联动)
|
||||||
|
string ext = Path.GetExtension(path).ToLowerInvariant();
|
||||||
|
bool isMatch = (_selFilterPrefab && ext == ".prefab")
|
||||||
|
|| (_selFilterScene && ext == ".unity")
|
||||||
|
|| (_selFilterSO && ext == ".asset")
|
||||||
|
|| (_selFilterTexture && (ext == ".png" || ext == ".jpg" || ext == ".tga"))
|
||||||
|
|| (_selFilterAudio && (ext == ".mp3" || ext == ".wav" || ext == ".ogg"));
|
||||||
|
// 若没有任何类型 Toggle 被勾选,则接受所有类型(兜底行为,避免全部筛空)
|
||||||
|
bool anyToggled = _selFilterPrefab || _selFilterScene || _selFilterSO || _selFilterTexture || _selFilterAudio;
|
||||||
|
if (anyToggled && !isMatch) return;
|
||||||
|
|
||||||
|
string addr = BuildAddress(path);
|
||||||
_selectionEntries.Add(new SelectionEntry
|
_selectionEntries.Add(new SelectionEntry
|
||||||
{
|
{
|
||||||
AssetPath = path,
|
AssetPath = path,
|
||||||
Guid = guid,
|
Guid = guid,
|
||||||
Address = BuildAddress(path),
|
Address = addr,
|
||||||
AlreadyRegistered = registeredGuids.Contains(guid),
|
AlreadyRegistered = registeredGuids.Contains(guid),
|
||||||
|
PredictedGroup = AddressableRules.GetExpectedGroup(addr),
|
||||||
|
PredictedLabels = FormatLabels(AddressableRules.GetExpectedLabels(addr)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,8 +709,24 @@ namespace BaseGames.Editor
|
|||||||
if (string.IsNullOrEmpty(guid)) return;
|
if (string.IsNullOrEmpty(guid)) return;
|
||||||
|
|
||||||
var settings = AddressableAssetSettingsDefaultObject.Settings;
|
var settings = AddressableAssetSettingsDefaultObject.Settings;
|
||||||
var group = groupNameOverride != null
|
|
||||||
? GetOrCreateGroup(settings, groupNameOverride)
|
// 重复地址检查:同一 address 已注册到不同 GUID 时提示确认
|
||||||
|
var existingByAddress = FindEntryByAddress(settings, address);
|
||||||
|
if (existingByAddress != null && existingByAddress.guid != guid)
|
||||||
|
{
|
||||||
|
bool proceed = EditorUtility.DisplayDialog(
|
||||||
|
"⚠ 地址已存在",
|
||||||
|
$"地址 \"{address}\" 已注册到:\n{existingByAddress.AssetPath}\n\n" +
|
||||||
|
$"继续会将该地址重新指向当前资产(GUID: {guid})。是否继续?",
|
||||||
|
"继续", "取消");
|
||||||
|
if (!proceed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine target group: explicit override → rules → manual selection
|
||||||
|
string effectiveGroup = groupNameOverride
|
||||||
|
?? (_applyRulesOnRegister ? AddressableRules.GetExpectedGroup(address) : null);
|
||||||
|
var group = effectiveGroup != null
|
||||||
|
? GetOrCreateGroup(settings, effectiveGroup)
|
||||||
: GetTargetGroup(settings);
|
: GetTargetGroup(settings);
|
||||||
if (group == null) return;
|
if (group == null) return;
|
||||||
|
|
||||||
@@ -601,6 +742,28 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_newLabel))
|
if (!string.IsNullOrWhiteSpace(_newLabel))
|
||||||
entry.SetLabel(_newLabel.Trim(), true, true);
|
entry.SetLabel(_newLabel.Trim(), true, true);
|
||||||
|
|
||||||
|
// Apply rules-based labels
|
||||||
|
if (_applyRulesOnRegister)
|
||||||
|
{
|
||||||
|
foreach (var lbl in AddressableRules.GetExpectedLabels(address))
|
||||||
|
{
|
||||||
|
EnsureLabelExists(settings, lbl);
|
||||||
|
entry.SetLabel(lbl, true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AddressableAssetEntry FindEntryByAddress(AddressableAssetSettings settings, string address)
|
||||||
|
{
|
||||||
|
if (settings == null) return null;
|
||||||
|
foreach (var group in settings.groups)
|
||||||
|
{
|
||||||
|
if (group == null) continue;
|
||||||
|
foreach (var e in group.entries)
|
||||||
|
if (e != null && e.address == address) return e;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AddressableAssetGroup GetOrCreateGroup(AddressableAssetSettings settings, string groupName)
|
private AddressableAssetGroup GetOrCreateGroup(AddressableAssetSettings settings, string groupName)
|
||||||
@@ -636,8 +799,36 @@ namespace BaseGames.Editor
|
|||||||
AddressableAssetSettings.ModificationEvent.EntryModified, null, true);
|
AddressableAssetSettings.ModificationEvent.EntryModified, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EnsureLabelExists(AddressableAssetSettings settings, string label)
|
||||||
|
{
|
||||||
|
if (!settings.GetLabels().Contains(label))
|
||||||
|
{
|
||||||
|
settings.AddLabel(label, true);
|
||||||
|
Debug.Log($"[AddressableBatch] 已创建标签:{label}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatLabels(string[] labels)
|
||||||
|
=> labels.Length > 0 ? string.Join(", ", labels) : "—";
|
||||||
|
|
||||||
// ══ 创建分组 ══════════════════════════════════════════════════════════
|
// ══ 创建分组 ══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
private void QuickScanGameFolder()
|
||||||
|
{
|
||||||
|
_folderPath = "Assets/_Game";
|
||||||
|
_folderAsset = AssetDatabase.LoadAssetAtPath<DefaultAsset>(_folderPath);
|
||||||
|
_includeSubfolders = true;
|
||||||
|
_filterPrefab = true;
|
||||||
|
_filterScene = true;
|
||||||
|
_filterSO = true;
|
||||||
|
_filterAudio = true;
|
||||||
|
_filterTexture = false;
|
||||||
|
_addressFormat = AddressFormat.FileName;
|
||||||
|
_applyRulesOnRegister = true;
|
||||||
|
_tab = 1;
|
||||||
|
ScanFolder();
|
||||||
|
}
|
||||||
|
|
||||||
private void ShowCreateGroupDialog()
|
private void ShowCreateGroupDialog()
|
||||||
{
|
{
|
||||||
_newGroupName = EditorInputDialog.Show("新建 Addressable 分组", "请输入分组名称:", _newGroupName);
|
_newGroupName = EditorInputDialog.Show("新建 Addressable 分组", "请输入分组名称:", _newGroupName);
|
||||||
@@ -685,13 +876,27 @@ namespace BaseGames.Editor
|
|||||||
private string BuildAddress(string assetPath)
|
private string BuildAddress(string assetPath)
|
||||||
{
|
{
|
||||||
string fileName = Path.GetFileNameWithoutExtension(assetPath);
|
string fileName = Path.GetFileNameWithoutExtension(assetPath);
|
||||||
|
string relativePath = MakeRelativePath(assetPath, _folderPath);
|
||||||
|
|
||||||
return _addressFormat switch
|
return _addressFormat switch
|
||||||
{
|
{
|
||||||
AddressFormat.FileName => fileName,
|
AddressFormat.FileName => fileName,
|
||||||
AddressFormat.FullAssetPath => assetPath,
|
AddressFormat.FullAssetPath => assetPath,
|
||||||
AddressFormat.RelativeToFolder => MakeRelativePath(assetPath, _folderPath),
|
AddressFormat.RelativeToFolder => relativePath,
|
||||||
AddressFormat.PrefixPlusFileName => _addressPrefix + fileName,
|
// 前缀拼接:前缀以 '/' 结尾时直接连接(如 "Config/" + "FootstepCatalog")
|
||||||
AddressFormat.PrefixPlusRelativePath=> _addressPrefix + MakeRelativePath(assetPath, _folderPath),
|
// 前缀不以 '/' 结尾时用 '_' 连接(如 "Room_Forest" + "_01")
|
||||||
|
AddressFormat.PrefixPlusFileName =>
|
||||||
|
string.IsNullOrEmpty(_addressPrefix)
|
||||||
|
? fileName
|
||||||
|
: (_addressPrefix.EndsWith("/")
|
||||||
|
? _addressPrefix + fileName
|
||||||
|
: _addressPrefix + "_" + fileName),
|
||||||
|
AddressFormat.PrefixPlusRelativePath =>
|
||||||
|
string.IsNullOrEmpty(_addressPrefix)
|
||||||
|
? relativePath
|
||||||
|
: (_addressPrefix.EndsWith("/")
|
||||||
|
? _addressPrefix + relativePath
|
||||||
|
: _addressPrefix + "_" + relativePath),
|
||||||
_ => fileName,
|
_ => fileName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -716,6 +921,29 @@ namespace BaseGames.Editor
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断路径是否为可寻址资产(排除脚本、程序集定义、Shader、Sprite Atlas、Material 等文件)。
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsAddressableAssetPath(string path)
|
||||||
|
{
|
||||||
|
string ext = Path.GetExtension(path);
|
||||||
|
if (string.IsNullOrEmpty(ext)) return false;
|
||||||
|
// 排除代码 / 元数据类文件
|
||||||
|
return ext != ".cs"
|
||||||
|
&& ext != ".asmdef"
|
||||||
|
&& ext != ".asmref"
|
||||||
|
&& ext != ".shader"
|
||||||
|
&& ext != ".hlsl"
|
||||||
|
&& ext != ".cginc"
|
||||||
|
&& ext != ".glsl"
|
||||||
|
&& ext != ".json"
|
||||||
|
&& ext != ".xml"
|
||||||
|
&& ext != ".txt"
|
||||||
|
&& ext != ".md"
|
||||||
|
&& ext != ".spriteatlas" // 随依赖它的 Prefab 隐式打包
|
||||||
|
&& ext != ".mat"; // Material 随 Prefab 依赖打包
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>从 AddressKey(如 "ENM_GruntWarrior")派生搜索名("GruntWarrior")。</summary>
|
/// <summary>从 AddressKey(如 "ENM_GruntWarrior")派生搜索名("GruntWarrior")。</summary>
|
||||||
private static string DeriveName(string key)
|
private static string DeriveName(string key)
|
||||||
{
|
{
|
||||||
@@ -740,27 +968,6 @@ namespace BaseGames.Editor
|
|||||||
return string.Equals(name, searchName, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(name, searchName, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 判断路径是否为可寻址资产(排除脚本、程序集定义、Shader 等代码文件)。
|
|
||||||
/// </summary>
|
|
||||||
private static bool IsAddressableAssetPath(string path)
|
|
||||||
{
|
|
||||||
string ext = Path.GetExtension(path);
|
|
||||||
if (string.IsNullOrEmpty(ext)) return false;
|
|
||||||
// 排除代码 / 元数据类文件
|
|
||||||
return ext != ".cs"
|
|
||||||
&& ext != ".asmdef"
|
|
||||||
&& ext != ".asmref"
|
|
||||||
&& ext != ".shader"
|
|
||||||
&& ext != ".hlsl"
|
|
||||||
&& ext != ".cginc"
|
|
||||||
&& ext != ".glsl"
|
|
||||||
&& ext != ".json"
|
|
||||||
&& ext != ".xml"
|
|
||||||
&& ext != ".txt"
|
|
||||||
&& ext != ".md";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitStyles()
|
private void InitStyles()
|
||||||
{
|
{
|
||||||
if (_stylesInitialized) return;
|
if (_stylesInitialized) return;
|
||||||
@@ -790,6 +997,8 @@ namespace BaseGames.Editor
|
|||||||
public string Guid;
|
public string Guid;
|
||||||
public string Address;
|
public string Address;
|
||||||
public bool AlreadyRegistered;
|
public bool AlreadyRegistered;
|
||||||
|
public string PredictedGroup;
|
||||||
|
public string PredictedLabels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SelectionEntry
|
private class SelectionEntry
|
||||||
@@ -798,6 +1007,8 @@ namespace BaseGames.Editor
|
|||||||
public string Guid;
|
public string Guid;
|
||||||
public string Address;
|
public string Address;
|
||||||
public bool AlreadyRegistered;
|
public bool AlreadyRegistered;
|
||||||
|
public string PredictedGroup;
|
||||||
|
public string PredictedLabels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum AddressFormat
|
private enum AddressFormat
|
||||||
|
|||||||
@@ -36,11 +36,13 @@ namespace BaseGames.Editor
|
|||||||
public string ExpectedGroup; // null = 规则未覆盖,维持现状
|
public string ExpectedGroup; // null = 规则未覆盖,维持现状
|
||||||
public string[] CurrentLabels;
|
public string[] CurrentLabels;
|
||||||
public string[] ExpectedLabels;
|
public string[] ExpectedLabels;
|
||||||
public string[] MissingLabels; // 应有但没有
|
public string[] MissingLabels; // 应有但没有(规则要求),红色错误
|
||||||
public string[] ExtraLabels; // 有但不应有
|
public string[] ExtraLabels; // 规则不要求且在 KnownLabels 中(多余规则标签),红色错误
|
||||||
|
public string[] UnknownLabels; // 规则不要求且不在 KnownLabels 中(自定义标签),黄色警告,不自动删除
|
||||||
public bool GroupOk => ExpectedGroup == null || CurrentGroup == ExpectedGroup;
|
public bool GroupOk => ExpectedGroup == null || CurrentGroup == ExpectedGroup;
|
||||||
public bool LabelsOk => MissingLabels.Length == 0 && ExtraLabels.Length == 0;
|
public bool LabelsOk => MissingLabels.Length == 0 && ExtraLabels.Length == 0;
|
||||||
public bool IsOk => GroupOk && LabelsOk;
|
public bool IsOk => GroupOk && LabelsOk;
|
||||||
|
public bool HasWarnings => UnknownLabels.Length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 状态 ──────────────────────────────────────────────────────────────
|
// ── 状态 ──────────────────────────────────────────────────────────────
|
||||||
@@ -74,7 +76,7 @@ namespace BaseGames.Editor
|
|||||||
public static void OpenWindow()
|
public static void OpenWindow()
|
||||||
{
|
{
|
||||||
var win = GetWindow<AddressableRuleSyncWindow>("Addressable Rule Sync");
|
var win = GetWindow<AddressableRuleSyncWindow>("Addressable Rule Sync");
|
||||||
win.minSize = new Vector2(900, 520);
|
win.minSize = new Vector2(1040, 540);
|
||||||
win.Show();
|
win.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +110,9 @@ namespace BaseGames.Editor
|
|||||||
if (GUILayout.Button("扫描", EditorStyles.toolbarButton, GUILayout.Width(80)))
|
if (GUILayout.Button("扫描", EditorStyles.toolbarButton, GUILayout.Width(80)))
|
||||||
Scan();
|
Scan();
|
||||||
|
|
||||||
|
if (GUILayout.Button("🔄 刷新", EditorStyles.toolbarButton, GUILayout.Width(60)))
|
||||||
|
Scan();
|
||||||
|
|
||||||
GUILayout.Space(8);
|
GUILayout.Space(8);
|
||||||
_showOk = GUILayout.Toggle(_showOk, "显示正常项", EditorStyles.toolbarButton, GUILayout.Width(80));
|
_showOk = GUILayout.Toggle(_showOk, "显示正常项", EditorStyles.toolbarButton, GUILayout.Width(80));
|
||||||
GUILayout.Space(8);
|
GUILayout.Space(8);
|
||||||
@@ -136,10 +141,12 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
int total = _reports.Count;
|
int total = _reports.Count;
|
||||||
int ok = _reports.Count(r => r.IsOk);
|
int ok = _reports.Count(r => r.IsOk);
|
||||||
int issues = total - ok;
|
int issues = _reports.Count(r => !r.IsOk);
|
||||||
|
int warnings = _reports.Count(r => r.IsOk && r.HasWarnings);
|
||||||
int wrongGrp = _reports.Count(r => !r.GroupOk);
|
int wrongGrp = _reports.Count(r => !r.GroupOk);
|
||||||
int misLabel = _reports.Count(r => r.MissingLabels.Length > 0);
|
int misLabel = _reports.Count(r => r.MissingLabels.Length > 0);
|
||||||
int extLabel = _reports.Count(r => r.ExtraLabels.Length > 0);
|
int extLabel = _reports.Count(r => r.ExtraLabels.Length > 0);
|
||||||
|
int unkLabel = _reports.Count(r => r.UnknownLabels.Length > 0);
|
||||||
|
|
||||||
EditorGUILayout.Space(2);
|
EditorGUILayout.Space(2);
|
||||||
using (new EditorGUILayout.HorizontalScope())
|
using (new EditorGUILayout.HorizontalScope())
|
||||||
@@ -148,9 +155,11 @@ namespace BaseGames.Editor
|
|||||||
GUILayout.Space(12);
|
GUILayout.Space(12);
|
||||||
DrawColoredLabel($"✅ 正常 {ok}", ColOk);
|
DrawColoredLabel($"✅ 正常 {ok}", ColOk);
|
||||||
GUILayout.Space(12);
|
GUILayout.Space(12);
|
||||||
DrawColoredLabel($"⚠ 问题 {issues}", issues > 0 ? ColWarn : ColOk);
|
DrawColoredLabel($"❌ 问题 {issues}", issues > 0 ? ColError : ColOk);
|
||||||
|
GUILayout.Space(8);
|
||||||
|
DrawColoredLabel($"⚠ 自定义标签 {unkLabel}", unkLabel > 0 ? ColWarn : ColOk);
|
||||||
GUILayout.Space(20);
|
GUILayout.Space(20);
|
||||||
GUILayout.Label($"分组错误 {wrongGrp} | 标签缺失 {misLabel} | 标签多余 {extLabel}",
|
GUILayout.Label($"分组错误 {wrongGrp} | 标签缺失 {misLabel} | 多余规则标签 {extLabel}",
|
||||||
EditorStyles.miniLabel);
|
EditorStyles.miniLabel);
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
}
|
}
|
||||||
@@ -165,10 +174,11 @@ namespace BaseGames.Editor
|
|||||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
|
||||||
{
|
{
|
||||||
GUILayout.Label("Address", _boldStyle, GUILayout.Width(200));
|
GUILayout.Label("Address", _boldStyle, GUILayout.Width(200));
|
||||||
GUILayout.Label("当前分组", _boldStyle, GUILayout.Width(130));
|
GUILayout.Label("当前分组", _boldStyle, GUILayout.Width(120));
|
||||||
GUILayout.Label("期望分组", _boldStyle, GUILayout.Width(130));
|
GUILayout.Label("期望分组", _boldStyle, GUILayout.Width(120));
|
||||||
GUILayout.Label("缺失标签", _boldStyle, GUILayout.Width(140));
|
GUILayout.Label("缺失标签", _boldStyle, GUILayout.Width(130));
|
||||||
GUILayout.Label("多余标签", _boldStyle, GUILayout.Width(120));
|
GUILayout.Label("多余规则标签", _boldStyle, GUILayout.Width(110));
|
||||||
|
GUILayout.Label("自定义标签", _boldStyle, GUILayout.Width(110));
|
||||||
GUILayout.Label("状态", _boldStyle, GUILayout.Width(80));
|
GUILayout.Label("状态", _boldStyle, GUILayout.Width(80));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,29 +221,35 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
// 当前分组
|
// 当前分组
|
||||||
var grpColor = r.GroupOk ? ColOk : ColError;
|
var grpColor = r.GroupOk ? ColOk : ColError;
|
||||||
DrawColoredLabel(r.CurrentGroup ?? "—", grpColor, GUILayout.Width(130));
|
DrawColoredLabel(r.CurrentGroup ?? "—", grpColor, GUILayout.Width(120));
|
||||||
|
|
||||||
// 期望分组
|
// 期望分组
|
||||||
var expGrpText = r.ExpectedGroup ?? "(规则未覆盖)";
|
var expGrpText = r.ExpectedGroup ?? "(规则未覆盖)";
|
||||||
var expGrpColor = r.GroupOk ? ColOk : ColWarn;
|
var expGrpColor = r.GroupOk ? ColOk : ColWarn;
|
||||||
DrawColoredLabel(expGrpText, expGrpColor, GUILayout.Width(130));
|
DrawColoredLabel(expGrpText, expGrpColor, GUILayout.Width(120));
|
||||||
|
|
||||||
// 缺失标签
|
// 缺失标签(红色,须补齐)
|
||||||
var missingText = r.MissingLabels.Length > 0 ? string.Join(", ", r.MissingLabels) : "—";
|
var missingText = r.MissingLabels.Length > 0 ? string.Join(", ", r.MissingLabels) : "—";
|
||||||
DrawColoredLabel(missingText, r.MissingLabels.Length > 0 ? ColError : ColOk, GUILayout.Width(140));
|
DrawColoredLabel(missingText, r.MissingLabels.Length > 0 ? ColError : ColOk, GUILayout.Width(130));
|
||||||
|
|
||||||
// 多余标签
|
// 多余规则标签(红色,将被 FixEntry 移除)
|
||||||
var extraText = r.ExtraLabels.Length > 0 ? string.Join(", ", r.ExtraLabels) : "—";
|
var extraText = r.ExtraLabels.Length > 0 ? string.Join(", ", r.ExtraLabels) : "—";
|
||||||
DrawColoredLabel(extraText, r.ExtraLabels.Length > 0 ? ColWarn : ColOk, GUILayout.Width(120));
|
DrawColoredLabel(extraText, r.ExtraLabels.Length > 0 ? ColError : ColOk, GUILayout.Width(110));
|
||||||
|
|
||||||
|
// 自定义标签(黄色警告,不会被自动删除,建议写入规范)
|
||||||
|
var unknownText = r.UnknownLabels.Length > 0 ? string.Join(", ", r.UnknownLabels) : "—";
|
||||||
|
DrawColoredLabel(unknownText, r.UnknownLabels.Length > 0 ? ColWarn : ColOk, GUILayout.Width(110));
|
||||||
|
|
||||||
// 状态 + 单条修复按钮
|
// 状态 + 单条修复按钮
|
||||||
if (r.IsOk)
|
if (r.IsOk)
|
||||||
{
|
{
|
||||||
DrawColoredLabel("✅ 正常", ColOk, GUILayout.Width(80));
|
var statusColor = r.HasWarnings ? ColWarn : ColOk;
|
||||||
|
var statusText = r.HasWarnings ? "⚠ 自定义标签" : "✅ 正常";
|
||||||
|
DrawColoredLabel(statusText, statusColor, GUILayout.Width(80));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DrawColoredLabel("⚠ 需修复", ColWarn, GUILayout.Width(60));
|
DrawColoredLabel("❌ 需修复", ColError, GUILayout.Width(60));
|
||||||
if (GUILayout.Button("修复", EditorStyles.miniButton, GUILayout.Width(40)))
|
if (GUILayout.Button("修复", EditorStyles.miniButton, GUILayout.Width(40)))
|
||||||
FixEntry(r);
|
FixEntry(r);
|
||||||
}
|
}
|
||||||
@@ -247,7 +263,8 @@ namespace BaseGames.Editor
|
|||||||
EditorGUILayout.Space(4);
|
EditorGUILayout.Space(4);
|
||||||
EditorGUILayout.HelpBox(
|
EditorGUILayout.HelpBox(
|
||||||
"规则来源:Docs/Standards/AddressablesLabelSpec.md §3 分组规则:AssetFolderSpec.md §8.1\n" +
|
"规则来源:Docs/Standards/AddressablesLabelSpec.md §3 分组规则:AssetFolderSpec.md §8.1\n" +
|
||||||
"「修复所有问题」仅修改已注册资产的分组/标签,不注册新资产(请用 Addressable Batch Tool)。",
|
"「修复所有问题」仅修改已注册资产的分组/标签,不注册新资产,不删除自定义标签(黄色警告项)。\n" +
|
||||||
|
"新增资产工作流:① Addressable Batch Tool → ⚡ 全量扫描 _Game/ → 注册所有 ② 返回此窗口 → 扫描 → 修复所有问题",
|
||||||
MessageType.None);
|
MessageType.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,9 +276,6 @@ namespace BaseGames.Editor
|
|||||||
var settings = AddressableAssetSettingsDefaultObject.Settings;
|
var settings = AddressableAssetSettingsDefaultObject.Settings;
|
||||||
if (settings == null) return;
|
if (settings == null) return;
|
||||||
|
|
||||||
// 收集所有全局标签供"多余标签"判断
|
|
||||||
var allKnownLabels = new HashSet<string>(settings.GetLabels(), StringComparer.Ordinal);
|
|
||||||
|
|
||||||
foreach (var group in settings.groups)
|
foreach (var group in settings.groups)
|
||||||
{
|
{
|
||||||
if (group == null) continue;
|
if (group == null) continue;
|
||||||
@@ -275,7 +289,13 @@ namespace BaseGames.Editor
|
|||||||
var currentLbls = entry.labels.ToArray();
|
var currentLbls = entry.labels.ToArray();
|
||||||
|
|
||||||
var missing = expectedLbls.Except(currentLbls, StringComparer.Ordinal).ToArray();
|
var missing = expectedLbls.Except(currentLbls, StringComparer.Ordinal).ToArray();
|
||||||
var extra = currentLbls.Except(expectedLbls, StringComparer.Ordinal).ToArray();
|
|
||||||
|
// 区分两类"多余标签":
|
||||||
|
// extra = 规则已知标签(KnownLabels)中规则不要求的 → 红色,FixEntry 会移除
|
||||||
|
// unknown = 不在 KnownLabels 中的自定义标签 → 黄色警告,FixEntry 保留,建议写入规范
|
||||||
|
var notExpected = currentLbls.Except(expectedLbls, StringComparer.Ordinal);
|
||||||
|
var extra = notExpected.Where(l => AddressableRules.KnownLabels.Contains(l)).ToArray();
|
||||||
|
var unknown = notExpected.Where(l => !AddressableRules.KnownLabels.Contains(l)).ToArray();
|
||||||
|
|
||||||
_reports.Add(new EntryReport
|
_reports.Add(new EntryReport
|
||||||
{
|
{
|
||||||
@@ -287,20 +307,24 @@ namespace BaseGames.Editor
|
|||||||
ExpectedLabels = expectedLbls,
|
ExpectedLabels = expectedLbls,
|
||||||
MissingLabels = missing,
|
MissingLabels = missing,
|
||||||
ExtraLabels = extra,
|
ExtraLabels = extra,
|
||||||
|
UnknownLabels = unknown,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 问题项排前面,正常项排后面;同类按 Address 字母序
|
// 问题项排前面,仅有警告的次之,正常项排最后;同类按 Address 字母序
|
||||||
_reports = _reports
|
_reports = _reports
|
||||||
.OrderBy(r => r.IsOk)
|
.OrderBy(r => r.IsOk ? (r.HasWarnings ? 1 : 2) : 0)
|
||||||
.ThenBy(r => r.Address, StringComparer.Ordinal)
|
.ThenBy(r => r.Address, StringComparer.Ordinal)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
_scanned = true;
|
_scanned = true;
|
||||||
Repaint();
|
Repaint();
|
||||||
|
|
||||||
|
int issues = _reports.Count(r => !r.IsOk);
|
||||||
|
int warnings = _reports.Count(r => r.IsOk && r.HasWarnings);
|
||||||
Debug.Log($"[AddressableRuleSync] 扫描完成:{_reports.Count} 个条目," +
|
Debug.Log($"[AddressableRuleSync] 扫描完成:{_reports.Count} 个条目," +
|
||||||
$"{_reports.Count(r => !r.IsOk)} 个需要修复。");
|
$"{issues} 个需要修复,{warnings} 个含自定义标签警告。");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 修复逻辑 ──────────────────────────────────────────────────────────
|
// ── 修复逻辑 ──────────────────────────────────────────────────────────
|
||||||
@@ -310,6 +334,22 @@ namespace BaseGames.Editor
|
|||||||
var issues = _reports.Where(r => !r.IsOk).ToList();
|
var issues = _reports.Where(r => !r.IsOk).ToList();
|
||||||
if (issues.Count == 0) return;
|
if (issues.Count == 0) return;
|
||||||
|
|
||||||
|
int moveCount = issues.Count(r => !r.GroupOk);
|
||||||
|
int addCount = issues.Sum(r => r.MissingLabels.Length);
|
||||||
|
int removeCount = issues.Sum(r => r.ExtraLabels.Length);
|
||||||
|
|
||||||
|
// 干跑预览对话框
|
||||||
|
bool confirmed = EditorUtility.DisplayDialog(
|
||||||
|
"确认修复所有问题",
|
||||||
|
$"将对 {issues.Count} 个条目执行以下操作:\n\n" +
|
||||||
|
$" • 移动分组:{moveCount} 个\n" +
|
||||||
|
$" • 添加标签:{addCount} 个\n" +
|
||||||
|
$" • 移除多余规则标签:{removeCount} 个\n\n" +
|
||||||
|
"⚠ 自定义标签(黄色警告项)不会被删除。\n" +
|
||||||
|
"此操作不可撤销,请确认后继续。",
|
||||||
|
"确认修复", "取消");
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
int fixedCount = 0;
|
int fixedCount = 0;
|
||||||
foreach (var r in issues)
|
foreach (var r in issues)
|
||||||
{
|
{
|
||||||
@@ -355,7 +395,8 @@ namespace BaseGames.Editor
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除多余标签
|
// 移除多余规则标签(ExtraLabels 只包含 KnownLabels 中规则不要求的标签;
|
||||||
|
// UnknownLabels 是用户自定义标签,刻意保留,不做删除)
|
||||||
foreach (var lbl in r.ExtraLabels)
|
foreach (var lbl in r.ExtraLabels)
|
||||||
{
|
{
|
||||||
entry.SetLabel(lbl, false, true);
|
entry.SetLabel(lbl, false, true);
|
||||||
|
|||||||
@@ -14,23 +14,57 @@ namespace BaseGames.Editor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class AddressableRules
|
public static class AddressableRules
|
||||||
{
|
{
|
||||||
|
// ── 已知标签白名单 ────────────────────────────────────────────────────────
|
||||||
|
// AddressableRuleSyncWindow 用此白名单区分:
|
||||||
|
// • 规则要求但缺失 → 红色,必须补齐
|
||||||
|
// • 规则不要求但存在且不在白名单 → 黄色警告(自定义标签,建议写进规范)
|
||||||
|
// • 规则不要求但存在且在白名单 → 白色,合法的人工附加标签
|
||||||
|
// 每次向 AddressablesLabelSpec 新增 Label 时,须同步在此处添加。
|
||||||
|
public static readonly HashSet<string> KnownLabels = new(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
AddressKeys.Labels.Preload,
|
||||||
|
AddressKeys.Labels.Poolable,
|
||||||
|
AddressKeys.Labels.Enemy,
|
||||||
|
AddressKeys.Labels.BGM,
|
||||||
|
AddressKeys.Labels.SFX,
|
||||||
|
AddressKeys.Labels.Charms,
|
||||||
|
AddressKeys.Labels.Config,
|
||||||
|
AddressKeys.Labels.Weapon,
|
||||||
|
};
|
||||||
|
|
||||||
// ── 前缀 → 分组名 ──────────────────────────────────────────────────────
|
// ── 前缀 → 分组名 ──────────────────────────────────────────────────────
|
||||||
// 规则:按 AssetFolderSpec §8.1 Group 划分策略。
|
// 规则:按 AssetFolderSpec §8.1 Group 划分策略。
|
||||||
// 顺序:更长/更具体的前缀必须排在更短/更泛化的前缀之前,否则短前缀会先匹配。
|
// 顺序:更长/更具体的前缀必须排在更短/更泛化的前缀之前,否则短前缀会先匹配。
|
||||||
// 特殊:Room_/Boss_ 地址的分组名在运行时动态计算,见 GetExpectedGroup()。
|
// 特殊:Room_/Boss_ 地址的分组名在运行时动态计算,见 GetExpectedGroup()。
|
||||||
public static readonly (string Prefix, string Group)[] PrefixGroupMap =
|
public static readonly (string Prefix, string Group)[] PrefixGroupMap =
|
||||||
{
|
{
|
||||||
|
// ── 场景 ─────────────────────────────────────────────────────────
|
||||||
("Scene_", "Scenes"),
|
("Scene_", "Scenes"),
|
||||||
|
// ── 玩家 & 武器 ──────────────────────────────────────────────────
|
||||||
("PLY_", "Player"),
|
("PLY_", "Player"),
|
||||||
("WPN_", "Player"), // 武器与玩家 Prefab 同组(AssetFolderSpec §8.1)
|
("WPN_", "Player"), // 武器与玩家 Prefab 同组(AssetFolderSpec §8.1)
|
||||||
|
// ── 敌人 & 投射物 ────────────────────────────────────────────────
|
||||||
("ENM_", "Enemies"),
|
("ENM_", "Enemies"),
|
||||||
("PROJ_", "Projectiles"),
|
("PROJ_", "Projectiles"),
|
||||||
("VFX_", "VFX_Common"), // 通用特效组(AssetFolderSpec §8.1)
|
// ── 特效 & UI ────────────────────────────────────────────────────
|
||||||
|
("VFX_", "VFX_Common"), // 通用特效(AssetFolderSpec §8.1)
|
||||||
("UI_", "UI"),
|
("UI_", "UI"),
|
||||||
|
// ── 收集物 ──────────────────────────────────────────────────────
|
||||||
("COL_", "Collectibles"),
|
("COL_", "Collectibles"),
|
||||||
("CHM_", "Config"), // 护身符 SO 归入 Config 组(AddressablesLabelSpec §3.9)
|
// ── 配置数据(更具体的前缀排在泛化前缀之前)──────────────────────
|
||||||
("Config/", "Config"),
|
("CHM_", "Config"), // 护身符 SO(AddressablesLabelSpec §3.9)
|
||||||
("AUD_", "Audio_Music"),
|
("SKL_", "Config"), // 技能配置 SO(AssetFolderSpec §4)
|
||||||
|
("SPL_", "Config"), // 法术配置 SO
|
||||||
|
("ABL_", "Config"), // 能力配置 SO
|
||||||
|
("MAP_", "Config"), // 地图数据 SO(AssetFolderSpec §4)
|
||||||
|
("Config/", "Config"), // 路径前缀配置(AssetFolderSpec §8.2)
|
||||||
|
// ── 音频(AUD_BGM_ / AUD_SFX_ 必须在通配 AUD_ 之前)─────────────
|
||||||
|
("AUD_BGM_", "Audio_Music"), // BGM 流式音频
|
||||||
|
("AUD_SFX_", "Audio_SFX"), // SFX 音效(独立分组便于包体按需加载)
|
||||||
|
("AUD_", "Audio_Music"), // 未细分音频归 BGM 组
|
||||||
|
// ── 世界 & 持久化 ────────────────────────────────────────────────
|
||||||
|
("WLD_", "World"), // 可交互世界物件 Prefab
|
||||||
|
("SYS_", "Persistent"), // Persistent 场景管理器 Prefab
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── 精确地址 → 标签(优先级高于前缀规则)────────────────────────────────
|
// ── 精确地址 → 标签(优先级高于前缀规则)────────────────────────────────
|
||||||
@@ -51,19 +85,33 @@ namespace BaseGames.Editor
|
|||||||
// 顺序:更具体的前缀(AUD_BGM_)在更泛化的前缀(AUD_)之前。
|
// 顺序:更具体的前缀(AUD_BGM_)在更泛化的前缀(AUD_)之前。
|
||||||
private static readonly (string Prefix, string[] Labels)[] PrefixLabelMap =
|
private static readonly (string Prefix, string[] Labels)[] PrefixLabelMap =
|
||||||
{
|
{
|
||||||
|
// ── 音频(更具体的 AUD_BGM_ / AUD_SFX_ 必须排在 AUD_ 之前)──────
|
||||||
("AUD_BGM_", new[] { AddressKeys.Labels.BGM }),
|
("AUD_BGM_", new[] { AddressKeys.Labels.BGM }),
|
||||||
("AUD_SFX_", new[] { AddressKeys.Labels.SFX }),
|
("AUD_SFX_", new[] { AddressKeys.Labels.SFX }),
|
||||||
("AUD_", new[] { AddressKeys.Labels.BGM }), // 未细分音频默认归 BGM
|
("AUD_", new[] { AddressKeys.Labels.BGM }), // 未细分音频默认归 BGM
|
||||||
("Scene_", Array.Empty<string>()), // 除 MainMenu 外场景无 label
|
// ── 场景(除 MainMenu 外无 label,由 ExactLabelMap 特殊处理)──────
|
||||||
|
("Scene_", Array.Empty<string>()),
|
||||||
|
// ── 玩家 & 武器 ──────────────────────────────────────────────────
|
||||||
("PLY_", new[] { AddressKeys.Labels.Preload }),
|
("PLY_", new[] { AddressKeys.Labels.Preload }),
|
||||||
("WPN_", new[] { AddressKeys.Labels.Weapon, AddressKeys.Labels.Preload }),
|
("WPN_", new[] { AddressKeys.Labels.Weapon, AddressKeys.Labels.Preload }),
|
||||||
|
// ── 敌人 & 投射物 ────────────────────────────────────────────────
|
||||||
("ENM_", new[] { AddressKeys.Labels.Enemy }),
|
("ENM_", new[] { AddressKeys.Labels.Enemy }),
|
||||||
("PROJ_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }),
|
("PROJ_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }),
|
||||||
|
// ── 特效 & UI ────────────────────────────────────────────────────
|
||||||
("VFX_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }),
|
("VFX_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }),
|
||||||
("UI_", Array.Empty<string>()), // 除 FloatingDamageText 外 UI 无默认 label
|
("UI_", Array.Empty<string>()), // 除 FloatingDamageText 外 UI 无默认 label
|
||||||
|
// ── 收集物 ──────────────────────────────────────────────────────
|
||||||
("COL_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }),
|
("COL_", new[] { AddressKeys.Labels.Poolable, AddressKeys.Labels.Preload }),
|
||||||
|
// ── 配置数据 ─────────────────────────────────────────────────────
|
||||||
("CHM_", new[] { AddressKeys.Labels.Charms }),
|
("CHM_", new[] { AddressKeys.Labels.Charms }),
|
||||||
|
("MAP_", new[] { AddressKeys.Labels.Config }), // 地图数据 SO 为动态加载配置
|
||||||
("Config/", new[] { AddressKeys.Labels.Config }),
|
("Config/", new[] { AddressKeys.Labels.Config }),
|
||||||
|
// ── 技能 / 法术 / 能力 / 世界物件 / 持久化:无批量加载需求,不加 Label ──
|
||||||
|
("SKL_", Array.Empty<string>()),
|
||||||
|
("SPL_", Array.Empty<string>()),
|
||||||
|
("ABL_", Array.Empty<string>()),
|
||||||
|
("WLD_", Array.Empty<string>()),
|
||||||
|
("SYS_", Array.Empty<string>()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── 公开 API ───────────────────────────────────────────────────────────
|
// ── 公开 API ───────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user