【工具】增加协议模拟工具
parent
7f19b4a4d6
commit
3e3a44b66b
|
|
@ -0,0 +1,706 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Gameplay.Net;
|
||||||
|
using Google.Protobuf;
|
||||||
|
using Google.Protobuf.Reflection;
|
||||||
|
using NLDMID;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class NetDebugWindow : EditorWindow
|
||||||
|
{
|
||||||
|
[MenuItem("Tools/网络协议调试")]
|
||||||
|
public static void ShowWindow()
|
||||||
|
{
|
||||||
|
var window = GetWindow<NetDebugWindow>("网络协议调试");
|
||||||
|
window.minSize = new Vector2(400f, 500f);
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MSGID> _clientMsgIds;
|
||||||
|
private List<string> _clientMsgNames;
|
||||||
|
private List<string> _filteredNames;
|
||||||
|
private List<MSGID> _filteredIds;
|
||||||
|
private int _selectedIndex = 0;
|
||||||
|
private string _searchText = "";
|
||||||
|
private Vector2 _fieldScrollPos;
|
||||||
|
private Vector2 _logScrollPos;
|
||||||
|
private List<string> _logEntries = new List<string>();
|
||||||
|
private Dictionary<string, bool> _foldoutStates = new Dictionary<string, bool>();
|
||||||
|
private Dictionary<Type, PropertyInfo> _mPBPropertyCache = new Dictionary<Type, PropertyInfo>();
|
||||||
|
private Dictionary<Type, FieldInfo> _mPBBackingFieldCache = new Dictionary<Type, FieldInfo>();
|
||||||
|
private MSGID _lastSelectedId = MSGID.None;
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
private void _Init()
|
||||||
|
{
|
||||||
|
if (_initialized) return;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
_clientMsgIds = new List<MSGID>();
|
||||||
|
_clientMsgNames = new List<string>();
|
||||||
|
|
||||||
|
foreach (MSGID id in Enum.GetValues(typeof(MSGID)))
|
||||||
|
{
|
||||||
|
string name = id.ToString();
|
||||||
|
if (name.StartsWith("Clt"))
|
||||||
|
{
|
||||||
|
_clientMsgIds.Add(id);
|
||||||
|
_clientMsgNames.Add($"{name} ({(int)id})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_filteredIds = new List<MSGID>(_clientMsgIds);
|
||||||
|
_filteredNames = new List<string>(_clientMsgNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PropertyInfo _GetmPBProperty(Type msgType)
|
||||||
|
{
|
||||||
|
if (!_mPBPropertyCache.TryGetValue(msgType, out PropertyInfo prop))
|
||||||
|
{
|
||||||
|
prop = msgType.GetProperty("mPB", BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
_mPBPropertyCache[msgType] = prop;
|
||||||
|
}
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FieldInfo _GetmPBBackingField(Type msgType)
|
||||||
|
{
|
||||||
|
if (!_mPBBackingFieldCache.TryGetValue(msgType, out FieldInfo field))
|
||||||
|
{
|
||||||
|
field = msgType.GetField("m_PB", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
_mPBBackingFieldCache[msgType] = field;
|
||||||
|
}
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
_Init();
|
||||||
|
|
||||||
|
bool isPlaying = EditorApplication.isPlaying;
|
||||||
|
bool netReady = isPlaying && NetManager.Instance != null && NetManager.Instance.mClient != null;
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox(
|
||||||
|
netReady ? "连接正常,可以发送协议" : "请先进入 Play 模式并确保网络连接正常",
|
||||||
|
netReady ? MessageType.Info : MessageType.Warning
|
||||||
|
);
|
||||||
|
|
||||||
|
EditorGUI.BeginDisabledGroup(!netReady);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
EditorGUILayout.LabelField("搜索协议", EditorStyles.boldLabel);
|
||||||
|
string newSearch = EditorGUILayout.TextField(_searchText);
|
||||||
|
if (newSearch != _searchText)
|
||||||
|
{
|
||||||
|
_searchText = newSearch;
|
||||||
|
_FilterMsgIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
EditorGUILayout.LabelField("选择协议ID", EditorStyles.boldLabel);
|
||||||
|
if (_filteredNames != null && _filteredNames.Count > 0)
|
||||||
|
{
|
||||||
|
_selectedIndex = EditorGUILayout.Popup("协议", _selectedIndex, _filteredNames.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
EditorGUILayout.LabelField("协议字段", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
Gameplay.Net.IMessage currentMsg = null;
|
||||||
|
object pbObj = null;
|
||||||
|
|
||||||
|
if (netReady && _filteredIds != null && _filteredIds.Count > 0 && _selectedIndex >= 0 && _selectedIndex < _filteredIds.Count)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MSGID selectedId = _filteredIds[_selectedIndex];
|
||||||
|
if (selectedId != _lastSelectedId)
|
||||||
|
{
|
||||||
|
_foldoutStates.Clear();
|
||||||
|
_lastSelectedId = selectedId;
|
||||||
|
}
|
||||||
|
currentMsg = NetManager.Instance.GetMessage(selectedId);
|
||||||
|
if (currentMsg != null)
|
||||||
|
{
|
||||||
|
PropertyInfo mPBProp = _GetmPBProperty(currentMsg.GetType());
|
||||||
|
if (mPBProp != null)
|
||||||
|
{
|
||||||
|
pbObj = mPBProp.GetValue(currentMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox($"获取协议消息失败: {e.Message}", MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pbObj != null)
|
||||||
|
{
|
||||||
|
_fieldScrollPos = EditorGUILayout.BeginScrollView(_fieldScrollPos, GUILayout.ExpandHeight(true));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_DrawProtobufMessage(pbObj, "");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox($"绘制协议字段失败: {e.Message}", MessageType.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EditorGUILayout.EndScrollView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (netReady)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("无法获取协议消息内容", MessageType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("发送协议", GUILayout.Height(30)))
|
||||||
|
{
|
||||||
|
_Send(currentMsg);
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("重置字段", GUILayout.Height(30)))
|
||||||
|
{
|
||||||
|
_Reset(currentMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EditorGUI.EndDisabledGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
EditorGUILayout.LabelField("发送日志", EditorStyles.boldLabel);
|
||||||
|
if (GUILayout.Button("清空日志"))
|
||||||
|
{
|
||||||
|
_logEntries.Clear();
|
||||||
|
}
|
||||||
|
_logScrollPos = EditorGUILayout.BeginScrollView(_logScrollPos, GUILayout.Height(120));
|
||||||
|
for (int i = _logEntries.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
EditorGUILayout.TextArea(_logEntries[i], GUILayout.ExpandWidth(true));
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndScrollView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _FilterMsgIds()
|
||||||
|
{
|
||||||
|
_filteredIds = new List<MSGID>();
|
||||||
|
_filteredNames = new List<string>();
|
||||||
|
for (int i = 0; i < _clientMsgIds.Count; i++)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_searchText) || _clientMsgNames[i].IndexOf(_searchText, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
{
|
||||||
|
_filteredIds.Add(_clientMsgIds[i]);
|
||||||
|
_filteredNames.Add(_clientMsgNames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_selectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _Send(Gameplay.Net.IMessage msg)
|
||||||
|
{
|
||||||
|
if (msg == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[NetDebug] 消息为空,无法发送");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (NetManager.Instance?.mClient == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[NetDebug] NetClient 不可用");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NetManager.Instance.mClient.SendMessage(msg);
|
||||||
|
string logEntry = $"[{DateTime.Now:HH:mm:ss}] 发送 {msg.ID}";
|
||||||
|
_logEntries.Add(logEntry);
|
||||||
|
Debug.Log($"[NetDebug] {logEntry}\n{msg}");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[NetDebug] 发送失败: {e.Message}\n{e.StackTrace}");
|
||||||
|
_logEntries.Add($"[{DateTime.Now:HH:mm:ss}] 发送失败: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _Reset(Gameplay.Net.IMessage msg)
|
||||||
|
{
|
||||||
|
if (msg == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FieldInfo backField = _GetmPBBackingField(msg.GetType());
|
||||||
|
if (backField != null)
|
||||||
|
{
|
||||||
|
backField.SetValue(msg, null);
|
||||||
|
}
|
||||||
|
_foldoutStates.Clear();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[NetDebug] 重置失败: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _DrawProtobufMessage(object pbObj, string pathPrefix)
|
||||||
|
{
|
||||||
|
if (pbObj == null) return;
|
||||||
|
|
||||||
|
Google.Protobuf.IMessage iMsg = pbObj as Google.Protobuf.IMessage;
|
||||||
|
if (iMsg == null) return;
|
||||||
|
|
||||||
|
MessageDescriptor descriptor = iMsg.Descriptor;
|
||||||
|
if (descriptor == null) return;
|
||||||
|
|
||||||
|
Type pbType = pbObj.GetType();
|
||||||
|
|
||||||
|
foreach (var field in descriptor.Fields.InFieldNumberOrder())
|
||||||
|
{
|
||||||
|
string csharpName = _ProtoNameToCSharpName(field.Name);
|
||||||
|
string fieldPath = string.IsNullOrEmpty(pathPrefix) ? csharpName : $"{pathPrefix}.{csharpName}";
|
||||||
|
PropertyInfo propInfo = pbType.GetProperty(csharpName, BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
|
||||||
|
if (field.IsRepeated)
|
||||||
|
{
|
||||||
|
_DrawRepeatedField(field, propInfo, pbObj, fieldPath);
|
||||||
|
}
|
||||||
|
else if (_IsMessageField(field))
|
||||||
|
{
|
||||||
|
_DrawNestedMessage(field, propInfo, pbObj, fieldPath);
|
||||||
|
}
|
||||||
|
else if (field.FieldType == FieldType.Enum)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (field.EnumType != null && field.EnumType.Values.Count > 0)
|
||||||
|
{
|
||||||
|
_DrawEnumField(field, propInfo, pbObj, fieldPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("无效的字段:" + field.Name, MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_DrawScalarField(field, propInfo, pbObj, fieldPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _DrawRepeatedField(FieldDescriptor field, PropertyInfo propInfo, object pbObj, string fieldPath)
|
||||||
|
{
|
||||||
|
if (propInfo == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(field.Name, "(无法访问属性)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IList list = propInfo.GetValue(pbObj) as IList;
|
||||||
|
if (list == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(field.Name, "(列表为 null)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string csharpName = _ProtoNameToCSharpName(field.Name);
|
||||||
|
string listLabel = $"{csharpName} [{list.Count}]";
|
||||||
|
bool foldout = _GetFoldoutState(fieldPath, true);
|
||||||
|
foldout = EditorGUILayout.Foldout(foldout, listLabel);
|
||||||
|
_SetFoldoutState(fieldPath, foldout);
|
||||||
|
|
||||||
|
if (!foldout) return;
|
||||||
|
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
|
bool isElementMsg = _IsMessageField(field);
|
||||||
|
|
||||||
|
for (int i = 0; i < list.Count; i++)
|
||||||
|
{
|
||||||
|
if (isElementMsg && list[i] != null)
|
||||||
|
{
|
||||||
|
bool itemFoldout = _GetFoldoutState($"{fieldPath}[{i}]", false);
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
itemFoldout = EditorGUILayout.Foldout(itemFoldout, $"[{i}]");
|
||||||
|
_SetFoldoutState($"{fieldPath}[{i}]", itemFoldout);
|
||||||
|
if (GUILayout.Button("×", GUILayout.Width(20)))
|
||||||
|
{
|
||||||
|
list.RemoveAt(i);
|
||||||
|
i--;
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
if (itemFoldout)
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
_DrawProtobufMessage(list[i], $"{fieldPath}[{i}]");
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
EditorGUILayout.LabelField($"[{i}]", GUILayout.Width(40));
|
||||||
|
object val = list[i];
|
||||||
|
object newVal = _DrawValueField(val, field, "");
|
||||||
|
if (newVal != null && !newVal.Equals(val))
|
||||||
|
{
|
||||||
|
try { list[i] = newVal; } catch { }
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("×", GUILayout.Width(20)))
|
||||||
|
{
|
||||||
|
list.RemoveAt(i);
|
||||||
|
i--;
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("+ 添加元素", GUILayout.Height(20)))
|
||||||
|
{
|
||||||
|
if (isElementMsg)
|
||||||
|
{
|
||||||
|
Type elementType = field.MessageType.ClrType;
|
||||||
|
if (elementType != null)
|
||||||
|
{
|
||||||
|
object newElem = Activator.CreateInstance(elementType);
|
||||||
|
list.Add(newElem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
object defaultVal = _GetDefaultForRepeatedElement(field);
|
||||||
|
if (defaultVal != null)
|
||||||
|
{
|
||||||
|
list.Add(defaultVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _DrawNestedMessage(FieldDescriptor field, PropertyInfo propInfo, object pbObj, string fieldPath)
|
||||||
|
{
|
||||||
|
string label = _ProtoNameToCSharpName(field.Name);
|
||||||
|
|
||||||
|
object subMsg = null;
|
||||||
|
if (propInfo != null)
|
||||||
|
{
|
||||||
|
subMsg = propInfo.GetValue(pbObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subMsg == null)
|
||||||
|
{
|
||||||
|
if (propInfo != null && _IsMessageField(field) && field.MessageType.ClrType != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
subMsg = Activator.CreateInstance(field.MessageType.ClrType);
|
||||||
|
propInfo.SetValue(pbObj, subMsg);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(label, $"(创建失败: {e.Message})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(label, "(无法创建子消息)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool foldout = _GetFoldoutState(fieldPath, false);
|
||||||
|
foldout = EditorGUILayout.Foldout(foldout, label);
|
||||||
|
_SetFoldoutState(fieldPath, foldout);
|
||||||
|
|
||||||
|
if (foldout)
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
_DrawProtobufMessage(subMsg, fieldPath);
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _DrawEnumField(FieldDescriptor field, PropertyInfo propInfo, object pbObj, string fieldPath)
|
||||||
|
{
|
||||||
|
if (propInfo == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(field.Name, "(无法访问属性)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string label = _ProtoNameToCSharpName(field.Name);
|
||||||
|
object currentVal = propInfo.GetValue(pbObj);
|
||||||
|
|
||||||
|
int currentInt = Convert.ToInt32(currentVal);
|
||||||
|
|
||||||
|
string[] displayOptions = new string[field.EnumType.Values.Count];
|
||||||
|
int[] values = new int[field.EnumType.Values.Count];
|
||||||
|
int selectedIdx = 0;
|
||||||
|
for (int i = 0; i < field.EnumType.Values.Count; i++)
|
||||||
|
{
|
||||||
|
displayOptions[i] = $"{field.EnumType.Values[i].Name} = {field.EnumType.Values[i].Number}";
|
||||||
|
values[i] = field.EnumType.Values[i].Number;
|
||||||
|
if (values[i] == currentInt) selectedIdx = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newIdx = EditorGUILayout.Popup(label, selectedIdx, displayOptions);
|
||||||
|
if (newIdx >= 0 && newIdx < values.Length && values[newIdx] != currentInt)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_SetPropertyValue(propInfo, pbObj, values[newIdx]);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[NetDebug] 设置枚举字段 {label} 失败: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _DrawScalarField(FieldDescriptor field, PropertyInfo propInfo, object pbObj, string fieldPath)
|
||||||
|
{
|
||||||
|
if (propInfo == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(field.Name, "(无法访问属性)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string label = _ProtoNameToCSharpName(field.Name);
|
||||||
|
object currentVal = propInfo.GetValue(pbObj);
|
||||||
|
if (currentVal == null) currentVal = _GetDefaultValue(field);
|
||||||
|
|
||||||
|
object newVal = _DrawValueField(currentVal, field, label);
|
||||||
|
if (newVal != null && !newVal.Equals(currentVal))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_SetPropertyValue(propInfo, pbObj, newVal);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[NetDebug] 设置字段 {label} 失败: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object _DrawValueField(object currentVal, FieldDescriptor field, string labelOverride)
|
||||||
|
{
|
||||||
|
string label = string.IsNullOrEmpty(labelOverride) ? _ProtoNameToCSharpName(field.Name) : labelOverride;
|
||||||
|
|
||||||
|
switch (field.FieldType)
|
||||||
|
{
|
||||||
|
case FieldType.UInt32:
|
||||||
|
case FieldType.Fixed32:
|
||||||
|
{
|
||||||
|
long val = Convert.ToInt64(currentVal);
|
||||||
|
long newVal = EditorGUILayout.LongField(label, val);
|
||||||
|
return (uint)Math.Max(0, newVal);
|
||||||
|
}
|
||||||
|
case FieldType.Int32:
|
||||||
|
case FieldType.SInt32:
|
||||||
|
case FieldType.SFixed32:
|
||||||
|
{
|
||||||
|
int val = Convert.ToInt32(currentVal);
|
||||||
|
return EditorGUILayout.IntField(label, val);
|
||||||
|
}
|
||||||
|
case FieldType.UInt64:
|
||||||
|
case FieldType.Fixed64:
|
||||||
|
{
|
||||||
|
long val = Convert.ToInt64(currentVal);
|
||||||
|
long newVal = EditorGUILayout.LongField(label, val);
|
||||||
|
return (ulong)newVal;
|
||||||
|
}
|
||||||
|
case FieldType.Int64:
|
||||||
|
case FieldType.SInt64:
|
||||||
|
case FieldType.SFixed64:
|
||||||
|
{
|
||||||
|
long val = Convert.ToInt64(currentVal);
|
||||||
|
return EditorGUILayout.LongField(label, val);
|
||||||
|
}
|
||||||
|
case FieldType.Float:
|
||||||
|
{
|
||||||
|
float val = Convert.ToSingle(currentVal);
|
||||||
|
return EditorGUILayout.FloatField(label, val);
|
||||||
|
}
|
||||||
|
case FieldType.Double:
|
||||||
|
{
|
||||||
|
double val = Convert.ToDouble(currentVal);
|
||||||
|
return EditorGUILayout.DoubleField(label, val);
|
||||||
|
}
|
||||||
|
case FieldType.Bool:
|
||||||
|
{
|
||||||
|
bool val = Convert.ToBoolean(currentVal);
|
||||||
|
return EditorGUILayout.Toggle(label, val);
|
||||||
|
}
|
||||||
|
case FieldType.String:
|
||||||
|
{
|
||||||
|
string val = currentVal as string ?? "";
|
||||||
|
return EditorGUILayout.TextField(label, val);
|
||||||
|
}
|
||||||
|
case FieldType.Bytes:
|
||||||
|
{
|
||||||
|
string val = currentVal is ByteString bs ? BitConverter.ToString(bs.ToByteArray()) : "";
|
||||||
|
EditorGUILayout.LabelField(label, "(bytes) " + val);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case FieldType.Enum:
|
||||||
|
{
|
||||||
|
int val = Convert.ToInt32(currentVal);
|
||||||
|
string[] displayOptions = new string[field.EnumType.Values.Count];
|
||||||
|
int[] values = new int[field.EnumType.Values.Count];
|
||||||
|
int selectedIdx = 0;
|
||||||
|
for (int i = 0; i < field.EnumType.Values.Count; i++)
|
||||||
|
{
|
||||||
|
displayOptions[i] = $"{field.EnumType.Values[i].Name} = {field.EnumType.Values[i].Number}";
|
||||||
|
values[i] = field.EnumType.Values[i].Number;
|
||||||
|
if (values[i] == val) selectedIdx = i;
|
||||||
|
}
|
||||||
|
int newIdx = EditorGUILayout.Popup(label, selectedIdx, displayOptions);
|
||||||
|
if (newIdx >= 0 && newIdx < values.Length) return values[newIdx];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
EditorGUILayout.LabelField(label, $"(不支持 {field.FieldType})");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object _GetDefaultValue(FieldDescriptor field)
|
||||||
|
{
|
||||||
|
switch (field.FieldType)
|
||||||
|
{
|
||||||
|
case FieldType.UInt32:
|
||||||
|
case FieldType.Fixed32: return (uint)0;
|
||||||
|
case FieldType.Int32:
|
||||||
|
case FieldType.SInt32:
|
||||||
|
case FieldType.SFixed32: return 0;
|
||||||
|
case FieldType.UInt64:
|
||||||
|
case FieldType.Fixed64: return (ulong)0;
|
||||||
|
case FieldType.Int64:
|
||||||
|
case FieldType.SInt64:
|
||||||
|
case FieldType.SFixed64: return (long)0;
|
||||||
|
case FieldType.Float: return 0f;
|
||||||
|
case FieldType.Double: return 0.0;
|
||||||
|
case FieldType.Bool: return false;
|
||||||
|
case FieldType.String: return "";
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object _GetDefaultForRepeatedElement(FieldDescriptor field)
|
||||||
|
{
|
||||||
|
switch (field.FieldType)
|
||||||
|
{
|
||||||
|
case FieldType.Int32:
|
||||||
|
case FieldType.SInt32:
|
||||||
|
case FieldType.SFixed32:
|
||||||
|
return 0;
|
||||||
|
case FieldType.UInt32:
|
||||||
|
case FieldType.Fixed32:
|
||||||
|
return (uint)0;
|
||||||
|
case FieldType.Int64:
|
||||||
|
case FieldType.SInt64:
|
||||||
|
case FieldType.SFixed64:
|
||||||
|
return (long)0;
|
||||||
|
case FieldType.UInt64:
|
||||||
|
case FieldType.Fixed64:
|
||||||
|
return (ulong)0;
|
||||||
|
case FieldType.Float:
|
||||||
|
return 0f;
|
||||||
|
case FieldType.Double:
|
||||||
|
return 0.0;
|
||||||
|
case FieldType.Bool:
|
||||||
|
return false;
|
||||||
|
case FieldType.String:
|
||||||
|
return "";
|
||||||
|
case FieldType.Enum:
|
||||||
|
if (field.EnumType != null && field.EnumType.Values.Count > 0)
|
||||||
|
return field.EnumType.Values[0].Number;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _SetPropertyValue(PropertyInfo propInfo, object target, object value)
|
||||||
|
{
|
||||||
|
Type propType = propInfo.PropertyType;
|
||||||
|
if (propType.IsEnum && value != null)
|
||||||
|
{
|
||||||
|
value = Enum.ToObject(propType, Convert.ToInt32(value));
|
||||||
|
}
|
||||||
|
else if (value != null && !propType.IsAssignableFrom(value.GetType()))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = Convert.ChangeType(value, propType);
|
||||||
|
}
|
||||||
|
catch (InvalidCastException)
|
||||||
|
{
|
||||||
|
// Conversion not possible, keep original value
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
propInfo.SetValue(target, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _IsMessageField(FieldDescriptor field)
|
||||||
|
{
|
||||||
|
return field.FieldType == FieldType.Message || field.FieldType == FieldType.Group;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _ProtoNameToCSharpName(string protoName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(protoName)) return protoName;
|
||||||
|
string[] parts = protoName.Split('_');
|
||||||
|
for (int i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
if (parts[i].Length > 0)
|
||||||
|
{
|
||||||
|
parts[i] = char.ToUpper(parts[i][0]) + parts[i].Substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string.Join("", parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _GetFoldoutState(string key, bool defaultValue)
|
||||||
|
{
|
||||||
|
if (_foldoutStates.TryGetValue(key, out bool val))
|
||||||
|
return val;
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _SetFoldoutState(string key, bool value)
|
||||||
|
{
|
||||||
|
_foldoutStates[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInspectorUpdate()
|
||||||
|
{
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7d36088dc69f41448a1dbb40a70e336e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Loading…
Reference in New Issue