File directory changes (mirror Scripts/ module structure): - AbilityTypeDrawer.cs → Equipment/ - CharacterWizardWindow.cs → Character/ - FormEditorWindow.cs → Player/ - GMToolWindow.cs → Tools/ - SOManagerWindow.cs → Tools/ - Map/MapRoomDataEditor.cs → World/Map/ - Navigation/ (root) → Enemies/Navigation/ - Achievements/ → Progression/ Menu hierarchy changes (BaseGames/ top-level): - Data/: +Character Wizard (from Tools/), +Boss Skill Sequence (from Tools/) - Addressables/: +Addressable Batch Tool, +Asset Reference Graph, +Validate Address Keys (from Tools/Verification/) - Scene/Setup/: +Boot Flow Wizard, +Scaffold *, +Auto-Open Persistent (from Tools/) - Scene/: +Camera Area Setup (from Camera/), +Bake All NavSurfaces (from Tools/) - Events/: +Event Bus Monitor, +Event Chain Viewer, +Create/Reimport Event Channels (from Tools/) - Tools/Validation/: +Validate All SOs, +Apply/Validate Script Order (from Tools/ flat) - Tools/Maintenance/: +Missing Scripts/*, +Physics2D Layer Matrix/* (from Tools/ flat) Result: BaseGames/Tools/ reduced from 16 flat items to 4 items + 2 submenus Docs: update AssetFolderSpec §12 editor tool table with new menu paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
160 lines
5.5 KiB
C#
160 lines
5.5 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
namespace BaseGames.Editor
|
||
{
|
||
/// <summary>
|
||
/// 一键应用/校验项目推荐的 Script Execution Order。
|
||
/// </summary>
|
||
public static class ScriptExecutionOrderTools
|
||
{
|
||
private readonly struct OrderRule
|
||
{
|
||
public readonly string ClassName;
|
||
public readonly int Order;
|
||
|
||
public OrderRule(string className, int order)
|
||
{
|
||
ClassName = className;
|
||
Order = order;
|
||
}
|
||
}
|
||
|
||
private static readonly OrderRule[] Rules =
|
||
{
|
||
new OrderRule("GameServiceRegistrar", -2000),
|
||
new OrderRule("GameManager", -1000),
|
||
new OrderRule("SceneService", -900),
|
||
new OrderRule("GameSaveManager", -900),
|
||
new OrderRule("AudioManager", -500),
|
||
new OrderRule("PlayerController", -100),
|
||
};
|
||
|
||
[MenuItem("BaseGames/Tools/Validation/Apply Script Execution Order Preset")]
|
||
public static void ApplyPreset()
|
||
{
|
||
int updated = 0;
|
||
int skipped = 0;
|
||
var issues = new List<string>();
|
||
|
||
foreach (var rule in Rules)
|
||
{
|
||
if (!TryFindMonoScript(rule.ClassName, out MonoScript script, out string issue))
|
||
{
|
||
skipped++;
|
||
issues.Add(issue);
|
||
continue;
|
||
}
|
||
|
||
int current = MonoImporter.GetExecutionOrder(script);
|
||
if (current == rule.Order)
|
||
continue;
|
||
|
||
MonoImporter.SetExecutionOrder(script, rule.Order);
|
||
updated++;
|
||
}
|
||
|
||
AssetDatabase.SaveAssets();
|
||
|
||
if (issues.Count > 0)
|
||
{
|
||
Debug.LogWarning(
|
||
"[ScriptExecutionOrderTools] 已应用执行顺序预设(部分脚本未处理)。\n" +
|
||
$"更新: {updated}, 跳过: {skipped}\n- {string.Join("\n- ", issues)}");
|
||
return;
|
||
}
|
||
|
||
Debug.Log($"[ScriptExecutionOrderTools] 执行顺序预设应用完成。更新数量: {updated}。");
|
||
}
|
||
|
||
[MenuItem("BaseGames/Tools/Validation/Validate Script Execution Order Preset")]
|
||
public static void ValidatePreset()
|
||
{
|
||
var mismatches = new List<string>();
|
||
var issues = new List<string>();
|
||
|
||
foreach (var rule in Rules)
|
||
{
|
||
if (!TryFindMonoScript(rule.ClassName, out MonoScript script, out string issue))
|
||
{
|
||
issues.Add(issue);
|
||
continue;
|
||
}
|
||
|
||
int current = MonoImporter.GetExecutionOrder(script);
|
||
if (current != rule.Order)
|
||
mismatches.Add($"{rule.ClassName}: 当前 {current}, 期望 {rule.Order}");
|
||
}
|
||
|
||
if (mismatches.Count == 0 && issues.Count == 0)
|
||
{
|
||
Debug.Log("[ScriptExecutionOrderTools] 执行顺序校验通过,所有脚本均符合预设。");
|
||
return;
|
||
}
|
||
|
||
string message = "[ScriptExecutionOrderTools] 执行顺序校验发现问题。";
|
||
if (mismatches.Count > 0)
|
||
message += "\n顺序不一致:\n- " + string.Join("\n- ", mismatches);
|
||
if (issues.Count > 0)
|
||
message += "\n脚本解析问题:\n- " + string.Join("\n- ", issues);
|
||
|
||
Debug.LogWarning(message);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据 <paramref name="className"/> 查找 MonoScript。
|
||
/// <para>当 className 包含 '.'(全限定名)时,用 <c>type.FullName</c> 精确匹配;
|
||
/// 否则用 <c>type.Name</c> 匹配(向后兼容简单类名)。</para>
|
||
/// </summary>
|
||
private static bool TryFindMonoScript(string className, out MonoScript script, out string issue)
|
||
{
|
||
script = null;
|
||
issue = null;
|
||
|
||
// 全限定名时,FindAssets 只取最后一段(简单类名)作为搜索词
|
||
bool useFullName = className.Contains('.');
|
||
string searchName = useFullName
|
||
? className.Substring(className.LastIndexOf('.') + 1)
|
||
: className;
|
||
|
||
string[] guids = AssetDatabase.FindAssets($"{searchName} t:MonoScript");
|
||
var matches = new List<MonoScript>();
|
||
|
||
foreach (string guid in guids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
var candidate = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
|
||
if (candidate == null)
|
||
continue;
|
||
|
||
Type type = candidate.GetClass();
|
||
if (type == null) continue;
|
||
|
||
bool nameMatch = useFullName
|
||
? type.FullName == className
|
||
: type.Name == className;
|
||
|
||
if (nameMatch)
|
||
matches.Add(candidate);
|
||
}
|
||
|
||
if (matches.Count == 0)
|
||
{
|
||
issue = $"未找到脚本: {className}";
|
||
return false;
|
||
}
|
||
|
||
if (matches.Count > 1)
|
||
{
|
||
issue = $"存在多个同名脚本: {className}(请消歧后重试)";
|
||
return false;
|
||
}
|
||
|
||
script = matches[0];
|
||
return true;
|
||
}
|
||
}
|
||
}
|