359 lines
12 KiB
C#
359 lines
12 KiB
C#
#if UNITY_EDITOR
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using UnityEditor;
|
||
using UnityEditor.Experimental.GraphView;
|
||
using UnityEditor.Experimental.SceneManagement;
|
||
using UnityEngine;
|
||
using AlicizaX.UI;
|
||
using AlicizaX.UI.Runtime;
|
||
using UnityEditor.SceneManagement;
|
||
using Random = UnityEngine.Random;
|
||
|
||
[CustomEditor(typeof(UXControllerStateRecorder))]
|
||
public class UXControllerStateRecorderEditor : Editor
|
||
{
|
||
private SerializedProperty _stateEntriesProp;
|
||
private SerializedProperty _controller;
|
||
private SerializedProperty _id;
|
||
private GUIContent _removeIcon;
|
||
private UXController _cacheController;
|
||
|
||
private void OnEnable()
|
||
{
|
||
_controller = serializedObject.FindProperty("_controller");
|
||
_id = serializedObject.FindProperty("_id");
|
||
_stateEntriesProp = serializedObject.FindProperty("_stateEntries");
|
||
_removeIcon = EditorGUIUtility.IconContent("d_winbtn_win_close");
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (target == null)
|
||
{
|
||
UnRegisterSelf();
|
||
}
|
||
}
|
||
|
||
private void UnRegisterSelf()
|
||
{
|
||
UXController controller = (_cacheController != null) ? _cacheController : GetPrefabStageController();
|
||
if (controller == null) return;
|
||
var so = new SerializedObject(controller);
|
||
var recorders = so.FindProperty("_recorders");
|
||
for (int i = recorders.arraySize - 1; i >= 0; i--)
|
||
{
|
||
var el = recorders.GetArrayElementAtIndex(i);
|
||
if (el.objectReferenceValue == null)
|
||
{
|
||
recorders.DeleteArrayElementAtIndex(i);
|
||
break;
|
||
}
|
||
}
|
||
|
||
so.ApplyModifiedProperties();
|
||
EditorUtility.SetDirty(controller);
|
||
}
|
||
|
||
private void RegisterSelfToController(UXController controller)
|
||
{
|
||
var so = new SerializedObject(controller);
|
||
var recorders = so.FindProperty("_recorders");
|
||
int newIndex = recorders.arraySize;
|
||
recorders.InsertArrayElementAtIndex(newIndex);
|
||
var el = recorders.GetArrayElementAtIndex(newIndex);
|
||
el.objectReferenceValue = target;
|
||
so.ApplyModifiedProperties();
|
||
EditorUtility.SetDirty(controller);
|
||
}
|
||
|
||
public override void OnInspectorGUI()
|
||
{
|
||
serializedObject.Update();
|
||
|
||
EditorGUILayout.Space();
|
||
|
||
var recorder = target as UXControllerStateRecorder;
|
||
UXController prefabCtrl = GetPrefabStageController();
|
||
|
||
if (_id.intValue <= 0)
|
||
{
|
||
_id.intValue = Random.Range(10000000, 99999999);
|
||
}
|
||
|
||
|
||
EditorGUILayout.LabelField($"ID:{_id.intValue}", EditorStyles.helpBox);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
|
||
if (prefabCtrl != null && prefabCtrl != _controller.objectReferenceValue)
|
||
{
|
||
_controller.objectReferenceValue = prefabCtrl;
|
||
}
|
||
|
||
if (!prefabCtrl.HasRecorder(recorder))
|
||
{
|
||
RegisterSelfToController(prefabCtrl);
|
||
}
|
||
|
||
if (_controller.objectReferenceValue != null)
|
||
{
|
||
_cacheController = _controller.objectReferenceValue as UXController;
|
||
}
|
||
|
||
|
||
if (_controller.objectReferenceValue != null)
|
||
{
|
||
UXController ctl = _controller.objectReferenceValue as UXController;
|
||
EditorGUILayout.LabelField($"{ctl.name} ({ctl.Controllers.Count} controllers)", EditorStyles.helpBox);
|
||
}
|
||
else
|
||
EditorGUILayout.LabelField("(None)", EditorStyles.helpBox);
|
||
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space();
|
||
|
||
Rect addRect = EditorGUILayout.GetControlRect(false, GUILayout.Height(22));
|
||
if (GUI.Button(addRect, "Add State"))
|
||
{
|
||
var provider = ScriptableObject.CreateInstance<StateTypeSearchProvider>();
|
||
provider.Init(OnTypeSelected);
|
||
var screenPos = GUIUtility.GUIToScreenPoint(new Vector2(addRect.x, addRect.yMax));
|
||
SearchWindow.Open(new SearchWindowContext(screenPos), provider);
|
||
}
|
||
|
||
EditorGUILayout.Space();
|
||
|
||
for (int i = 0; i < _stateEntriesProp.arraySize; i++)
|
||
{
|
||
var entryProp = _stateEntriesProp.GetArrayElementAtIndex(i);
|
||
if (entryProp == null) continue;
|
||
|
||
var stateProp = entryProp.FindPropertyRelative("State");
|
||
var controllerNameProp = entryProp.FindPropertyRelative("ControllerName");
|
||
var controllerIndexProp = entryProp.FindPropertyRelative("ControllerIndex");
|
||
|
||
if (stateProp == null)
|
||
{
|
||
EditorGUILayout.HelpBox($"Unable to find 'State' on entry {i}.", MessageType.Error);
|
||
if (GUILayout.Button("Remove"))
|
||
{
|
||
_stateEntriesProp.DeleteArrayElementAtIndex(i);
|
||
break;
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||
var t = stateProp.managedReferenceValue.GetType();
|
||
var attr = t.GetCustomAttribute<ControlerStateNameAttribute>();
|
||
var tag = attr != null ? attr.StateName : t.Name;
|
||
EditorGUILayout.LabelField($"State:{tag}", EditorStyles.helpBox);
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
|
||
var controllerNames = GetControllerNamesForRecorder(recorder);
|
||
int selIdx = -1;
|
||
if (!string.IsNullOrEmpty(controllerNameProp.stringValue))
|
||
{
|
||
selIdx = controllerNames.IndexOf(controllerNameProp.stringValue);
|
||
}
|
||
|
||
GUILayoutOption ctrlWidth = GUILayout.Width(140);
|
||
if (controllerNames.Count > 0)
|
||
{
|
||
int newSel = EditorGUILayout.Popup(selIdx, controllerNames.ToArray(), ctrlWidth);
|
||
if (newSel >= 0 && newSel < controllerNames.Count)
|
||
controllerNameProp.stringValue = controllerNames[newSel];
|
||
}
|
||
else
|
||
{
|
||
if (!string.IsNullOrEmpty(controllerNameProp.stringValue))
|
||
{
|
||
EditorGUI.BeginDisabledGroup(true);
|
||
EditorGUILayout.HelpBox($"绑定控制器 [{controllerNameProp.stringValue}] 不存在", MessageType.Error);
|
||
EditorGUI.EndDisabledGroup();
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.HelpBox($"暂无控制器", MessageType.Warning);
|
||
}
|
||
}
|
||
|
||
|
||
int ctrlLen = GetSelectedControllerLength(recorder, controllerNameProp.stringValue);
|
||
if (ctrlLen <= 0) ctrlLen = 1;
|
||
controllerIndexProp.intValue = Mathf.Clamp(controllerIndexProp.intValue, 0, ctrlLen - 1);
|
||
string[] idxOptions = new string[ctrlLen];
|
||
for (int j = 0; j < ctrlLen; j++) idxOptions[j] = j.ToString();
|
||
int curIdx = Mathf.Clamp(controllerIndexProp.intValue, 0, ctrlLen - 1);
|
||
|
||
if (controllerNames.Count > 0)
|
||
{
|
||
int newIdx = EditorGUILayout.Popup(curIdx, idxOptions, GUILayout.Width(60));
|
||
if (newIdx != curIdx) controllerIndexProp.intValue = newIdx;
|
||
}
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
|
||
if (GUILayout.Button(_removeIcon, GUILayout.Width(28), GUILayout.Height(20)))
|
||
{
|
||
_stateEntriesProp.DeleteArrayElementAtIndex(i);
|
||
break;
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(4);
|
||
|
||
if (stateProp.managedReferenceValue == null)
|
||
{
|
||
EditorGUILayout.HelpBox("State instance is null. Remove and re-add.", MessageType.Warning);
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||
EditorGUILayout.LabelField(new GUIContent("属性"), EditorStyles.boldLabel);
|
||
SerializedClassDrawer.DrawSerializableProperty(stateProp);
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
bool valid = true;
|
||
try
|
||
{
|
||
var inst = stateProp.managedReferenceValue as ControllerStateBase;
|
||
if (inst != null) valid = inst.Valid(recorder);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
valid = false;
|
||
Debug.LogException(ex);
|
||
}
|
||
|
||
if (!valid)
|
||
{
|
||
EditorGUILayout.HelpBox($"当前状态核验失败 请查看{stateProp.managedReferenceValue.GetType().FullName}脚本Valid", MessageType.Error);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
EditorGUILayout.Space();
|
||
}
|
||
|
||
serializedObject.ApplyModifiedProperties();
|
||
}
|
||
|
||
|
||
private void OnTypeSelected(Type type)
|
||
{
|
||
if (type == null) return;
|
||
|
||
// 撤销支持
|
||
Undo.RecordObject(serializedObject.targetObject, "Add Controller State");
|
||
|
||
serializedObject.Update();
|
||
|
||
int idx = _stateEntriesProp.arraySize;
|
||
_stateEntriesProp.InsertArrayElementAtIndex(idx);
|
||
var entryProp = _stateEntriesProp.GetArrayElementAtIndex(idx);
|
||
var stateProp = entryProp.FindPropertyRelative("State");
|
||
var controllerNameProp = entryProp.FindPropertyRelative("ControllerName");
|
||
var controllerIndexProp = entryProp.FindPropertyRelative("ControllerIndex");
|
||
var defaultValueProp = entryProp.FindPropertyRelative("DefaultValue");
|
||
ControllerStateBase instance = null;
|
||
try
|
||
{
|
||
instance = (ControllerStateBase)Activator.CreateInstance(type);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogException(ex);
|
||
}
|
||
|
||
if (instance != null)
|
||
{
|
||
var recorder = serializedObject.targetObject as UXControllerStateRecorder;
|
||
instance.Init(recorder);
|
||
}
|
||
|
||
// 把实例写进 SerializedProperty(需保证你的 State 字段用了 [SerializeReference])
|
||
if (stateProp != null) stateProp.managedReferenceValue = instance;
|
||
if (stateProp != null) stateProp.isExpanded = true;
|
||
controllerNameProp.stringValue = "";
|
||
controllerIndexProp.intValue = 0;
|
||
|
||
serializedObject.ApplyModifiedProperties();
|
||
EditorUtility.SetDirty(serializedObject.targetObject);
|
||
}
|
||
|
||
|
||
private UXController GetPrefabStageController()
|
||
{
|
||
var stage = PrefabStageUtility.GetCurrentPrefabStage();
|
||
if (stage == null) return null;
|
||
var root = stage.prefabContentsRoot;
|
||
if (root == null) return null;
|
||
return root.GetComponentInChildren<UXController>();
|
||
}
|
||
|
||
private List<string> GetControllerNamesForRecorder(UXControllerStateRecorder recorder)
|
||
{
|
||
var names = new List<string>();
|
||
var prefabCtrl = GetPrefabStageController();
|
||
if (prefabCtrl != null)
|
||
{
|
||
foreach (var cd in prefabCtrl.Controllers) names.Add(cd.Name);
|
||
return names;
|
||
}
|
||
|
||
var assigned = recorder.Controller;
|
||
if (assigned != null)
|
||
{
|
||
foreach (var cd in assigned.Controllers) names.Add(cd.Name);
|
||
return names;
|
||
}
|
||
|
||
var inScene = recorder.gameObject.GetComponentInParent<UXController>();
|
||
if (inScene != null)
|
||
{
|
||
foreach (var cd in inScene.Controllers) names.Add(cd.Name);
|
||
return names;
|
||
}
|
||
|
||
return names;
|
||
}
|
||
|
||
private int GetSelectedControllerLength(UXControllerStateRecorder recorder, string controllerName)
|
||
{
|
||
var prefabCtrl = GetPrefabStageController();
|
||
if (prefabCtrl != null)
|
||
{
|
||
var ch = prefabCtrl.Controllers.ToList().Find(x => x.Name == controllerName);
|
||
if (ch != null) return ch.Length;
|
||
}
|
||
|
||
var assigned = recorder.Controller;
|
||
if (assigned != null)
|
||
{
|
||
var ch = assigned.Controllers.ToList().Find(x => x.Name == controllerName);
|
||
if (ch != null) return ch.Length;
|
||
}
|
||
|
||
var inScene = recorder.gameObject.GetComponentInParent<UXController>();
|
||
if (inScene != null)
|
||
{
|
||
var ch = inScene.Controllers.ToList().Find(x => x.Name == controllerName);
|
||
if (ch != null) return ch.Length;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
}
|
||
#endif
|