Add WeaponFeedback component and AddressableManagerWindow meta file

- Implemented WeaponFeedback class for handling weapon-related feedbacks such as hit effects and attack sounds.
- Added meta file for AddressableManagerWindow to manage addressable assets.
- Included a new jump.data file for profiler data.
This commit is contained in:
2026-05-22 22:03:32 +08:00
parent 3e1f234ddc
commit b7baf7ad6a
44 changed files with 1783 additions and 1927 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: abd3b31b261435e4786f53b937c71742
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,527 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
// 此文件已被 AddressableManagerWindow 取代。
// 原有功能已整合到统一工具中,请使用:
// BaseGames → Addressables → Addressables Manager(总入口)
// BaseGames → Addressables → Rule Sync(直达规则校验 Tab
namespace BaseGames.Editor
{
/// <summary>
/// Addressable 规则同步窗口。
///
/// 功能:
/// 1. 扫描所有已注册的 Addressable 资产
/// 2. 根据 <see cref="AddressableRules"/> 中的规则计算期望分组与期望标签
/// 3. 对比实际值,显示所有不符合规范的条目(分组错误 / 标签缺失 / 标签多余)
/// 4. 一键自动修复全部问题
/// 5. 导出 CSV 报告供存档或 Code Review
///
/// 菜单:BaseGames → Addressables → Rule Sync
/// </summary>
public class AddressableRuleSyncWindow : EditorWindow
{
// ── 内部数据结构 ───────────────────────────────────────────────────────
private enum IssueKind { None, WrongGroup, MissingLabel, ExtraLabel }
private class EntryReport
{
public string Address;
public string AssetPath;
public string CurrentGroup;
public string ExpectedGroup; // null = 规则未覆盖,维持现状
public string[] CurrentLabels;
public string[] ExpectedLabels;
public string[] MissingLabels; // 应有但没有(规则要求),红色错误
public string[] ExtraLabels; // 规则不要求且在 KnownLabels 中(多余规则标签),红色错误
public string[] UnknownLabels; // 规则不要求且不在 KnownLabels 中(自定义标签),黄色警告,不自动删除
public bool GroupOk => ExpectedGroup == null || CurrentGroup == ExpectedGroup;
public bool LabelsOk => MissingLabels.Length == 0 && ExtraLabels.Length == 0;
public bool IsOk => GroupOk && LabelsOk;
public bool HasWarnings => UnknownLabels.Length > 0;
}
// ── 状态 ──────────────────────────────────────────────────────────────
private List<EntryReport> _reports = new();
private Vector2 _scrollPos;
private bool _showOk = false;
private bool _scanned = false;
private string _searchFilter = "";
// ── 样式(惰性初始化)────────────────────────────────────────────────
private GUIStyle _okStyle;
private GUIStyle _warnStyle;
private GUIStyle _errorStyle;
private GUIStyle _boldStyle;
private GUIStyle _rowEven;
private GUIStyle _rowOdd;
private bool _stylesReady;
// ── 颜色 ─────────────────────────────────────────────────────────────
private static readonly Color ColOk = new(0.20f, 0.78f, 0.35f, 1f);
private static readonly Color ColWarn = new(0.95f, 0.75f, 0.10f, 1f);
private static readonly Color ColError = new(0.90f, 0.25f, 0.20f, 1f);
private static readonly Color ColRowEven = new(0.22f, 0.22f, 0.22f, 0.4f);
// ── 菜单入口 ──────────────────────────────────────────────────────────
[MenuItem("BaseGames/Addressables/Rule Sync", priority = 110)]
public static void OpenWindow()
{
var win = GetWindow<AddressableRuleSyncWindow>("Addressable Rule Sync");
win.minSize = new Vector2(1040, 540);
win.Show();
}
// ── GUI ───────────────────────────────────────────────────────────────
private void OnGUI()
{
EnsureStyles();
if (AddressableAssetSettingsDefaultObject.Settings == null)
{
EditorGUILayout.HelpBox(
"Addressable Settings 未初始化。\n" +
"请先执行 Window → Asset Management → Addressables → Groups → Create Addressables Settings。",
MessageType.Error);
return;
}
DrawToolbar();
DrawStats();
DrawTable();
DrawFooter();
}
// ── 工具栏 ────────────────────────────────────────────────────────────
private void DrawToolbar()
{
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
{
if (GUILayout.Button("扫描", EditorStyles.toolbarButton, GUILayout.Width(80)))
Scan();
if (GUILayout.Button("🔄 刷新", EditorStyles.toolbarButton, GUILayout.Width(60)))
Scan();
GUILayout.Space(8);
_showOk = GUILayout.Toggle(_showOk, "显示正常项", EditorStyles.toolbarButton, GUILayout.Width(80));
GUILayout.Space(8);
EditorGUILayout.LabelField("搜索:", GUILayout.Width(42));
_searchFilter = EditorGUILayout.TextField(_searchFilter, EditorStyles.toolbarSearchField,
GUILayout.Width(200));
GUILayout.FlexibleSpace();
GUI.enabled = _scanned && _reports.Any(r => !r.IsOk);
if (GUILayout.Button("✦ 修复所有问题", EditorStyles.toolbarButton, GUILayout.Width(120)))
FixAll();
GUI.enabled = _scanned;
if (GUILayout.Button("导出 CSV", EditorStyles.toolbarButton, GUILayout.Width(80)))
ExportCsv();
GUI.enabled = true;
}
}
// ── 统计行 ────────────────────────────────────────────────────────────
private void DrawStats()
{
if (!_scanned) return;
int total = _reports.Count;
int ok = _reports.Count(r => r.IsOk);
int issues = _reports.Count(r => !r.IsOk);
int warnings = _reports.Count(r => r.IsOk && r.HasWarnings);
int wrongGrp = _reports.Count(r => !r.GroupOk);
int misLabel = _reports.Count(r => r.MissingLabels.Length > 0);
int extLabel = _reports.Count(r => r.ExtraLabels.Length > 0);
int unkLabel = _reports.Count(r => r.UnknownLabels.Length > 0);
EditorGUILayout.Space(2);
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Label($"共 {total} 条目", EditorStyles.miniLabel);
GUILayout.Space(12);
DrawColoredLabel($"✅ 正常 {ok}", ColOk);
GUILayout.Space(12);
DrawColoredLabel($"❌ 问题 {issues}", issues > 0 ? ColError : ColOk);
GUILayout.Space(8);
DrawColoredLabel($"⚠ 自定义标签 {unkLabel}", unkLabel > 0 ? ColWarn : ColOk);
GUILayout.Space(20);
GUILayout.Label($"分组错误 {wrongGrp} | 标签缺失 {misLabel} | 多余规则标签 {extLabel}",
EditorStyles.miniLabel);
GUILayout.FlexibleSpace();
}
EditorGUILayout.Space(2);
}
// ── 主表格 ────────────────────────────────────────────────────────────
private void DrawTable()
{
// 表头
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
{
GUILayout.Label("Address", _boldStyle, GUILayout.Width(200));
GUILayout.Label("当前分组", _boldStyle, GUILayout.Width(120));
GUILayout.Label("期望分组", _boldStyle, GUILayout.Width(120));
GUILayout.Label("缺失标签", _boldStyle, GUILayout.Width(130));
GUILayout.Label("多余规则标签", _boldStyle, GUILayout.Width(110));
GUILayout.Label("自定义标签", _boldStyle, GUILayout.Width(110));
GUILayout.Label("状态", _boldStyle, GUILayout.Width(80));
}
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, GUILayout.ExpandHeight(true));
if (!_scanned)
{
EditorGUILayout.HelpBox("点击「扫描」按钮开始分析已注册的 Addressable 资产。", MessageType.Info);
}
else
{
var display = _reports
.Where(r => _showOk || !r.IsOk)
.Where(r => string.IsNullOrEmpty(_searchFilter)
|| r.Address.IndexOf(_searchFilter, StringComparison.OrdinalIgnoreCase) >= 0)
.ToList();
if (display.Count == 0)
{
EditorGUILayout.HelpBox(
_showOk ? "没有匹配搜索条件的条目。" : "✅ 所有资产均符合规范!",
MessageType.Info);
}
for (int i = 0; i < display.Count; i++)
DrawRow(display[i], i);
}
EditorGUILayout.EndScrollView();
}
private void DrawRow(EntryReport r, int idx)
{
var bg = idx % 2 == 0 ? _rowEven : GUIStyle.none;
using (new EditorGUILayout.HorizontalScope(bg, GUILayout.Height(20)))
{
// Address(点击可 Ping
if (GUILayout.Button(r.Address, EditorStyles.linkLabel, GUILayout.Width(200)))
PingAsset(r.AssetPath);
// 当前分组
var grpColor = r.GroupOk ? ColOk : ColError;
DrawColoredLabel(r.CurrentGroup ?? "—", grpColor, GUILayout.Width(120));
// 期望分组
var expGrpText = r.ExpectedGroup ?? "(规则未覆盖)";
var expGrpColor = r.GroupOk ? ColOk : ColWarn;
DrawColoredLabel(expGrpText, expGrpColor, GUILayout.Width(120));
// 缺失标签(红色,须补齐)
var missingText = r.MissingLabels.Length > 0 ? string.Join(", ", r.MissingLabels) : "—";
DrawColoredLabel(missingText, r.MissingLabels.Length > 0 ? ColError : ColOk, GUILayout.Width(130));
// 多余规则标签(红色,将被 FixEntry 移除)
var extraText = r.ExtraLabels.Length > 0 ? string.Join(", ", r.ExtraLabels) : "—";
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)
{
var statusColor = r.HasWarnings ? ColWarn : ColOk;
var statusText = r.HasWarnings ? "⚠ 自定义标签" : "✅ 正常";
DrawColoredLabel(statusText, statusColor, GUILayout.Width(80));
}
else
{
DrawColoredLabel("❌ 需修复", ColError, GUILayout.Width(60));
if (GUILayout.Button("修复", EditorStyles.miniButton, GUILayout.Width(40)))
FixEntry(r);
}
}
}
// ── 底栏 ──────────────────────────────────────────────────────────────
private void DrawFooter()
{
EditorGUILayout.Space(4);
EditorGUILayout.HelpBox(
"规则来源:Docs/Standards/AddressablesLabelSpec.md §3 分组规则:AssetFolderSpec.md §8.1\n" +
"「修复所有问题」仅修改已注册资产的分组/标签,不注册新资产,不删除自定义标签(黄色警告项)。\n" +
"新增资产工作流:① Addressable Batch Tool → ⚡ 全量扫描 _Game/ → 注册所有 ② 返回此窗口 → 扫描 → 修复所有问题",
MessageType.None);
}
// ── 扫描逻辑 ──────────────────────────────────────────────────────────
private void Scan()
{
_reports.Clear();
var settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null) return;
foreach (var group in settings.groups)
{
if (group == null) continue;
foreach (var entry in group.entries)
{
if (entry == null) continue;
var address = entry.address;
var expectedGroup = AddressableRules.GetExpectedGroup(address);
var expectedLbls = AddressableRules.GetExpectedLabels(address);
var currentLbls = entry.labels.ToArray();
var missing = expectedLbls.Except(currentLbls, 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
{
Address = address,
AssetPath = entry.AssetPath,
CurrentGroup = group.name,
ExpectedGroup = expectedGroup,
CurrentLabels = currentLbls,
ExpectedLabels = expectedLbls,
MissingLabels = missing,
ExtraLabels = extra,
UnknownLabels = unknown,
});
}
}
// 问题项排前面,仅有警告的次之,正常项排最后;同类按 Address 字母序
_reports = _reports
.OrderBy(r => r.IsOk ? (r.HasWarnings ? 1 : 2) : 0)
.ThenBy(r => r.Address, StringComparer.Ordinal)
.ToList();
_scanned = true;
Repaint();
int issues = _reports.Count(r => !r.IsOk);
int warnings = _reports.Count(r => r.IsOk && r.HasWarnings);
Debug.Log($"[AddressableRuleSync] 扫描完成:{_reports.Count} 个条目," +
$"{issues} 个需要修复,{warnings} 个含自定义标签警告。");
}
// ── 修复逻辑 ──────────────────────────────────────────────────────────
private void FixAll()
{
var issues = _reports.Where(r => !r.IsOk).ToList();
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;
foreach (var r in issues)
{
if (FixEntry(r)) fixedCount++;
}
SaveSettings();
Scan(); // 修复后重新扫描以更新结果
Debug.Log($"[AddressableRuleSync] 修复完成:共处理 {fixedCount} 个条目。");
}
private bool FixEntry(EntryReport r)
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null) return false;
var entry = FindEntry(settings, r.Address);
if (entry == null)
{
Debug.LogWarning($"[AddressableRuleSync] 找不到条目:{r.Address}");
return false;
}
bool changed = false;
// 修复分组
if (!r.GroupOk && r.ExpectedGroup != null)
{
var targetGroup = GetOrCreateGroup(settings, r.ExpectedGroup);
if (targetGroup != null && entry.parentGroup != targetGroup)
{
settings.MoveEntry(entry, targetGroup, false, false);
r.CurrentGroup = r.ExpectedGroup;
changed = true;
}
}
// 添加缺失标签
foreach (var lbl in r.MissingLabels)
{
EnsureLabelExists(settings, lbl);
entry.SetLabel(lbl, true, true);
changed = true;
}
// 移除多余规则标签(ExtraLabels 只包含 KnownLabels 中规则不要求的标签;
// UnknownLabels 是用户自定义标签,刻意保留,不做删除)
foreach (var lbl in r.ExtraLabels)
{
entry.SetLabel(lbl, false, true);
changed = true;
}
return changed;
}
// ── 导出 CSV ──────────────────────────────────────────────────────────
private void ExportCsv()
{
if (_reports.Count == 0) return;
var path = EditorUtility.SaveFilePanel(
"导出 Addressable Rule 报告", "", "AddressableRuleReport.csv", "csv");
if (string.IsNullOrEmpty(path)) return;
var sb = new StringBuilder();
sb.AppendLine("Address,CurrentGroup,ExpectedGroup,GroupOk,MissingLabels,ExtraLabels,Status");
foreach (var r in _reports)
{
var status = r.IsOk ? "OK" : "ISSUE";
sb.AppendLine(
$"\"{r.Address}\"," +
$"\"{r.CurrentGroup}\"," +
$"\"{r.ExpectedGroup ?? "(uncovered)"}\"," +
$"{r.GroupOk}," +
$"\"{string.Join(";", r.MissingLabels)}\"," +
$"\"{string.Join(";", r.ExtraLabels)}\"," +
$"{status}");
}
File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
Debug.Log($"[AddressableRuleSync] CSV 报告已导出:{path}");
}
// ── 辅助方法 ──────────────────────────────────────────────────────────
private static AddressableAssetEntry FindEntry(AddressableAssetSettings settings, string address)
{
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 static AddressableAssetGroup GetOrCreateGroup(AddressableAssetSettings settings, string groupName)
{
var existing = settings.groups.FirstOrDefault(g => g != null && g.name == groupName);
if (existing != null) return existing;
var template = settings.GroupTemplateObjects.FirstOrDefault()
as AddressableAssetGroupTemplate;
var newGroup = settings.CreateGroup(groupName, false, false, true,
template != null
? new List<AddressableAssetGroupSchema>(template.SchemaObjects)
: null);
if (newGroup != null)
Debug.Log($"[AddressableRuleSync] 已自动创建分组:{groupName}");
return newGroup ?? settings.DefaultGroup;
}
private static void EnsureLabelExists(AddressableAssetSettings settings, string label)
{
var labels = settings.GetLabels();
if (!labels.Contains(label))
{
settings.AddLabel(label, true);
Debug.Log($"[AddressableRuleSync] 已创建标签:{label}");
}
}
private static void SaveSettings()
{
AssetDatabase.SaveAssets();
AddressableAssetSettingsDefaultObject.Settings?.SetDirty(
AddressableAssetSettings.ModificationEvent.EntryModified, null, true);
}
private static void PingAsset(string assetPath)
{
if (string.IsNullOrEmpty(assetPath)) return;
var obj = AssetDatabase.LoadMainAssetAtPath(assetPath);
if (obj != null) EditorGUIUtility.PingObject(obj);
}
// ── 样式初始化 ────────────────────────────────────────────────────────
private void EnsureStyles()
{
if (_stylesReady) return;
_boldStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 11 };
_okStyle = new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = ColOk } };
_warnStyle = new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = ColWarn } };
_errorStyle = new GUIStyle(EditorStyles.miniLabel) { normal = { textColor = ColError } };
_rowEven = new GUIStyle();
_rowEven.normal.background = MakeTexture(1, 1, ColRowEven);
_stylesReady = true;
}
private void DrawColoredLabel(string text, Color color, params GUILayoutOption[] options)
{
var prev = GUI.color;
GUI.color = color;
GUILayout.Label(text, EditorStyles.miniLabel, options);
GUI.color = prev;
}
private static Texture2D MakeTexture(int width, int height, Color color)
{
var tex = new Texture2D(width, height);
tex.SetPixel(0, 0, color);
tex.Apply();
return tex;
}
}
// 保留空类以避免 .meta 文件孤立。
internal static class AddressableRuleSyncWindowStub { }
}