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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: abd3b31b261435e4786f53b937c71742
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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 { }
}