UI相关优化补充
This commit is contained in:
8
Assets/_Game/Scripts/Editor/Inspector.meta
Normal file
8
Assets/_Game/Scripts/Editor/Inspector.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb77d9e506b335e46860387632561634
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
58
Assets/_Game/Scripts/Editor/Inspector/RequiredFieldDrawer.cs
Normal file
58
Assets/_Game/Scripts/Editor/Inspector/RequiredFieldDrawer.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using BaseGames.Core;
|
||||
|
||||
namespace BaseGames.Editor.Inspector
|
||||
{
|
||||
/// <summary>
|
||||
/// [RequiredField] 的 Inspector 绘制:未赋值时显示红色 HelpBox。
|
||||
/// </summary>
|
||||
[CustomPropertyDrawer(typeof(RequiredFieldAttribute))]
|
||||
public class RequiredFieldDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
bool missing = IsMissing(property);
|
||||
if (missing)
|
||||
{
|
||||
var msgHeight = EditorGUIUtility.singleLineHeight * 1.6f;
|
||||
var msgRect = new Rect(position.x, position.y, position.width, msgHeight);
|
||||
var attr = (RequiredFieldAttribute)attribute;
|
||||
var hint = string.IsNullOrEmpty(attr.Hint) ? "" : $" ({attr.Hint})";
|
||||
EditorGUI.HelpBox(msgRect, $"必填字段未赋值{hint}", MessageType.Error);
|
||||
|
||||
var fieldRect = new Rect(position.x, position.y + msgHeight + 2, position.width,
|
||||
EditorGUIUtility.singleLineHeight);
|
||||
var prev = GUI.color; GUI.color = new Color(1f, 0.6f, 0.6f);
|
||||
EditorGUI.PropertyField(fieldRect, property, label);
|
||||
GUI.color = prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.PropertyField(position, property, label, true);
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
bool missing = IsMissing(property);
|
||||
float baseH = EditorGUI.GetPropertyHeight(property, label, true);
|
||||
return missing ? baseH + EditorGUIUtility.singleLineHeight * 1.6f + 4f : baseH;
|
||||
}
|
||||
|
||||
private static bool IsMissing(SerializedProperty p)
|
||||
{
|
||||
switch (p.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
return p.objectReferenceValue == null;
|
||||
case SerializedPropertyType.String:
|
||||
return string.IsNullOrEmpty(p.stringValue);
|
||||
case SerializedPropertyType.ExposedReference:
|
||||
return p.exposedReferenceValue == null;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fbd662bb1183a549b6b2c63b8fd86f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
189
Assets/_Game/Scripts/Editor/Modules/EventChannelModule.cs
Normal file
189
Assets/_Game/Scripts/Editor/Modules/EventChannelModule.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BaseGames.Editor.Modules
|
||||
{
|
||||
/// <summary>
|
||||
/// DataHub 事件频道反查模块 —— 列出项目中所有 EventChannelSO 子类资产;
|
||||
/// 选中后可扫描全项目中引用该频道的 Prefab / ScriptableObject,
|
||||
/// 解决"谁发布/谁订阅了某条事件"在大型项目中难以追踪的问题。
|
||||
///
|
||||
/// 反查实现:遍历所有 Prefab 与 SO 资产,用 AssetDatabase.GetDependencies
|
||||
/// 检查依赖链是否包含目标频道资产路径。首次扫描会耗时较长(取决于资产规模),
|
||||
/// 之后按选中频道增量计算。
|
||||
/// </summary>
|
||||
public class EventChannelModule : IDataModule, IDataModuleOrdered
|
||||
{
|
||||
public string ModuleId => "eventchannel";
|
||||
public string DisplayName => "事件频道";
|
||||
public string IconName => "d_AudioMixerController Icon";
|
||||
public int DisplayOrder => 200;
|
||||
|
||||
private ListView _channelList;
|
||||
private readonly List<EventChannelEntry> _allChannels = new();
|
||||
private ScriptableObject _selected;
|
||||
private VisualElement _refsContainer;
|
||||
|
||||
private struct EventChannelEntry
|
||||
{
|
||||
public string TypeName;
|
||||
public string AssetPath;
|
||||
public ScriptableObject Asset;
|
||||
}
|
||||
|
||||
public void Initialize() => Refresh();
|
||||
|
||||
public void OnActivated() => Refresh();
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
_allChannels.Clear();
|
||||
// 扫描所有 ScriptableObject 子类,筛选名称以 "EventChannelSO" 结尾的(项目约定)。
|
||||
var types = TypeCache.GetTypesDerivedFrom<ScriptableObject>();
|
||||
foreach (var t in types)
|
||||
{
|
||||
if (t.IsAbstract) continue;
|
||||
if (!t.Name.EndsWith("EventChannelSO", StringComparison.Ordinal)) continue;
|
||||
var guids = AssetDatabase.FindAssets($"t:{t.Name}");
|
||||
foreach (var g in guids)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(g);
|
||||
var asset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path);
|
||||
if (asset == null) continue;
|
||||
_allChannels.Add(new EventChannelEntry
|
||||
{
|
||||
TypeName = t.Name,
|
||||
AssetPath = path,
|
||||
Asset = asset
|
||||
});
|
||||
}
|
||||
}
|
||||
_allChannels.Sort((a, b) =>
|
||||
{
|
||||
int c = string.Compare(a.TypeName, b.TypeName, StringComparison.Ordinal);
|
||||
return c != 0 ? c : string.Compare(a.AssetPath, b.AssetPath, StringComparison.Ordinal);
|
||||
});
|
||||
_channelList?.Rebuild();
|
||||
}
|
||||
|
||||
public void BuildListPane(VisualElement container, Action<UnityEngine.Object> onSelected)
|
||||
{
|
||||
var header = new Label($"频道总数:{_allChannels.Count}");
|
||||
header.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
header.style.paddingLeft = 6;
|
||||
header.style.paddingTop = 4;
|
||||
container.Add(header);
|
||||
|
||||
_channelList = new ListView(_allChannels, 20,
|
||||
() => new Label { style = { paddingLeft = 6 } },
|
||||
(ve, i) =>
|
||||
{
|
||||
var entry = _allChannels[i];
|
||||
var label = (Label)ve;
|
||||
label.text = $"[{entry.TypeName}] {System.IO.Path.GetFileNameWithoutExtension(entry.AssetPath)}";
|
||||
label.tooltip = entry.AssetPath;
|
||||
});
|
||||
_channelList.style.flexGrow = 1;
|
||||
_channelList.selectionChanged += sel =>
|
||||
{
|
||||
var first = sel.FirstOrDefault();
|
||||
if (first is EventChannelEntry e)
|
||||
{
|
||||
_selected = e.Asset;
|
||||
onSelected?.Invoke(e.Asset);
|
||||
}
|
||||
};
|
||||
container.Add(_channelList);
|
||||
}
|
||||
|
||||
public void BuildDetailPane(VisualElement container, UnityEngine.Object selected)
|
||||
{
|
||||
_selected = selected as ScriptableObject;
|
||||
container.Clear();
|
||||
|
||||
if (_selected == null)
|
||||
{
|
||||
var hint = new Label("请在左侧选择一条频道资产。");
|
||||
hint.style.paddingLeft = 8; hint.style.paddingTop = 8;
|
||||
container.Add(hint);
|
||||
return;
|
||||
}
|
||||
|
||||
// Header
|
||||
var title = new Label($"{_selected.GetType().Name} · {_selected.name}");
|
||||
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
title.style.fontSize = 14;
|
||||
title.style.paddingLeft = 8; title.style.paddingTop = 6;
|
||||
container.Add(title);
|
||||
|
||||
var pathLabel = new Label(AssetDatabase.GetAssetPath(_selected))
|
||||
{
|
||||
style = { paddingLeft = 8, paddingBottom = 6, color = new StyleColor(new Color(0.7f, 0.7f, 0.7f)) }
|
||||
};
|
||||
container.Add(pathLabel);
|
||||
|
||||
var scanBtn = new Button(() => ScanReferences(_selected))
|
||||
{ text = "扫描全项目引用(Prefab / ScriptableObject)" };
|
||||
scanBtn.style.marginLeft = 8; scanBtn.style.marginRight = 8;
|
||||
container.Add(scanBtn);
|
||||
|
||||
_refsContainer = new VisualElement();
|
||||
_refsContainer.style.paddingLeft = 8; _refsContainer.style.paddingTop = 6;
|
||||
container.Add(_refsContainer);
|
||||
}
|
||||
|
||||
private void ScanReferences(ScriptableObject channel)
|
||||
{
|
||||
if (channel == null || _refsContainer == null) return;
|
||||
_refsContainer.Clear();
|
||||
|
||||
var targetPath = AssetDatabase.GetAssetPath(channel);
|
||||
if (string.IsNullOrEmpty(targetPath)) return;
|
||||
|
||||
var found = new List<string>();
|
||||
try
|
||||
{
|
||||
EditorUtility.DisplayProgressBar("扫描引用", "正在扫描 Prefab 与 SO …", 0f);
|
||||
|
||||
var prefabGuids = AssetDatabase.FindAssets("t:Prefab");
|
||||
var soGuids = AssetDatabase.FindAssets("t:ScriptableObject");
|
||||
var all = prefabGuids.Concat(soGuids).ToArray();
|
||||
|
||||
for (int i = 0; i < all.Length; i++)
|
||||
{
|
||||
var p = AssetDatabase.GUIDToAssetPath(all[i]);
|
||||
if (p == targetPath) continue;
|
||||
if (i % 64 == 0)
|
||||
EditorUtility.DisplayProgressBar("扫描引用", p, (float)i / all.Length);
|
||||
|
||||
var deps = AssetDatabase.GetDependencies(p, recursive: false);
|
||||
if (System.Array.IndexOf(deps, targetPath) >= 0)
|
||||
found.Add(p);
|
||||
}
|
||||
}
|
||||
finally { EditorUtility.ClearProgressBar(); }
|
||||
|
||||
var summary = new Label($"找到 {found.Count} 个直接引用:");
|
||||
summary.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
_refsContainer.Add(summary);
|
||||
|
||||
foreach (var path in found)
|
||||
{
|
||||
var btn = new Button(() =>
|
||||
{
|
||||
var obj = AssetDatabase.LoadMainAssetAtPath(path);
|
||||
EditorGUIUtility.PingObject(obj);
|
||||
Selection.activeObject = obj;
|
||||
})
|
||||
{ text = path };
|
||||
btn.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||
btn.style.marginTop = 2;
|
||||
_refsContainer.Add(btn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f9fe819b8566b04494f41329e5e37e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user