Files
plugin-library/Assets/06.UIFarme/RunTime/UIFramework/UIControlBinding/Scripts/UIControlData.cs

741 lines
24 KiB
C#
Raw Normal View History

2025-09-23 11:18:38 +08:00
/*
URL: https://github.com/Misaka-Mikoto-Tech/UIControlBinding
使:
UE: UI根节点
: 齿 "复制代码到剪贴板"
UIManager
`` C#
IBindableUI uiA = Activator.CreateInstance(Type.GetType("UIA")) as IBindableUI;
GameObject prefab = Resources.Load<GameObject>("UI/UIA"); // you can get ui config from config file
GameObject go = Instantiate(prefab);
UIControlData ctrlData = go.GetComponent<UIControlData>();
if(ctrlData != null)
{
ctrlData.BindDataTo(uiA);
}
``
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using System.Text;
#if XLUA
using XLua;
#endif
using UnityEngine.Profiling;
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.Playables;
#endif
namespace Stary.Evo
{
/// <summary>
/// 单个控件数据
/// </summary>
[Serializable]
public class CtrlItemData
{
public string name = string.Empty;
#if UNITY_EDITOR
[HideInInspector]
public string type = string.Empty;
#endif
public UnityEngine.Object[] targets = new UnityEngine.Object[1];
public override string ToString()
{
return name;
}
}
/// <summary>
/// 单个子UI数据
/// </summary>
[Serializable]
public class SubUIItemData
{
public string name = string.Empty;
public UIControlData subUIData = null;
public override string ToString()
{
return name;
}
}
/// <summary>
/// 被绑定的UI类字段信息
/// </summary>
public class UIFieldsInfo
{
public Type type;
public List<FieldInfo> controls = new List<FieldInfo>(10);
public List<FieldInfo> subUIs = new List<FieldInfo>();
}
/// <summary>
/// 当前UI所有的绑定数据以及子UI指定
/// </summary>
[DisallowMultipleComponent]
public class UIControlData : MonoBehaviour
{
/// <summary>
/// 所有绑定的组件,不允许重名
/// </summary>
public List<CtrlItemData> ctrlItemDatas;
/// <summary>
/// 子UI数据
/// </summary>
public List<SubUIItemData> subUIItemDatas;
/// <summary>
/// 被绑定的UI
/// </summary>
public List<WeakReference<IBindableUI>> bindUIRefs;
/// <summary>
/// 缓存所有打开过的UI类型的字段数据如果有需求可以在特定时机清理以节约内存
/// </summary>
public static Dictionary<Type, UIFieldsInfo> s_uiFieldsCache = new Dictionary<Type, UIFieldsInfo>();
#region Editor
#if UNITY_EDITOR
/// <summary>
/// 已知类型列表,自定义类型可以添加到下面指定区域
/// </summary>
private static Dictionary<string, Type> _typeMap = new Dictionary<string, Type>()
{
{ "TextMeshProUGUI", typeof(TMPro.TextMeshProUGUI) },
{ "TextMeshPro", typeof(TMPro.TextMeshPro) },
{ "TMP_InputField", typeof(TMPro.TMP_InputField) },
{ "TMP_Dropdown", typeof(TMPro.TMP_Dropdown) },
{ "Text", typeof(Text)},
{ "RawImage", typeof(RawImage)},
{ "Button", typeof(Button)},
{ "Toggle", typeof(Toggle)},
{ "Slider", typeof(Slider)},
{ "Scrollbar", typeof(Scrollbar)},
{ "Dropdown", typeof(Dropdown)},
{ "InputField", typeof(InputField)},
{ "Canvas", typeof(Canvas)},
{ "UIScrollView", typeof(UIScrollView) },
{ "ScrollRect", typeof(ScrollRect)},
{ "SpriteRenderer", typeof(SpriteRenderer)},
{ "GridLayoutGroup", typeof(GridLayoutGroup) },
{ "Animation", typeof(Animation) },
{ "VideoPlayer", typeof(UnityEngine.Video.VideoPlayer) },
{ "CanvasGroup", typeof(CanvasGroup) },
{ "PlayableDirector", typeof(PlayableDirector) },
{ "UITweener", typeof(UITweener) },
////////自定义控件类型请放这里////////
//////////////////////////////////////
{ "Image", typeof(Image)},
{ "RectTransform", typeof(RectTransform)},
{ "Transform", typeof(Transform)},
{ "GameObject", typeof(GameObject)},
};
public static string[] GetAllTypeNames()
{
string[] keys = new string[_typeMap.Count + 1];
keys[0] = "自动";
_typeMap.Keys.CopyTo(keys, 1);
return keys;
}
public static Type[] GetAllTypes()
{
Type[] types = new Type[_typeMap.Count + 1];
types[0] = typeof(UnityEngine.Object);
_typeMap.Values.CopyTo(types, 1);
return types;
}
#endif
#endregion
#region BindDataToC#UI
/// <summary>
/// 将当前数据绑定到某窗口类实例的字段UI 加载后必须被执行
/// </summary>
/// <param name="ui">需要绑定数据的 UI</param>
public void BindDataTo(IBindableUI ui)
{
if (ui == null)
return;
#if DEBUG_LOG
float time = Time.realtimeSinceStartup;
Profiler.BeginSample("BindDataTo");
#endif
UIFieldsInfo fieldInfos = GetUIFieldsInfo(ui.GetType());
var controls = fieldInfos.controls;
for (int i = 0, imax = controls.Count; i < imax; i++)
{
try
{
BindCtrl(ui, controls[i]);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
var subUIs = fieldInfos.subUIs;
for (int i = 0, imax = subUIs.Count; i < imax; i++)
BindSubUI(ui, subUIs[i]);
if (bindUIRefs == null)
bindUIRefs = new List<WeakReference<IBindableUI>>();
bindUIRefs.Add(new WeakReference<IBindableUI>(ui));
#if DEBUG_LOG
Profiler.EndSample();
float span = Time.realtimeSinceStartup - time;
if (span > 0.002f)
Debug.LogWarningFormat("BindDataTo {0} 耗时{1}ms", ui.GetType().Name, span * 1000f);
#endif
}
private void BindCtrl(IBindableUI ui, FieldInfo fi)
{
int itemIdx = GetCtrlIndex(fi.Name);
if (itemIdx == -1)
{
Debug.LogWarningFormat("can not find binding control of name [{0}] in prefab", fi.Name);
return;
}
var objs = ctrlItemDatas[itemIdx];
Type fieldType = fi.FieldType;
if (fieldType.IsArray)
{
Array arrObj = Array.CreateInstance(fieldType.GetElementType(), objs.targets.Length);
// 给数组元素设置数据
for (int j = 0, jmax = objs.targets.Length; j < jmax; j++)
{
if (objs.targets[j] != null)
arrObj.SetValue(objs.targets[j], j);
else
Debug.LogErrorFormat("Component {0}[{1}] is null", objs.name, j);
}
fi.SetValue(ui, arrObj);
}
else
{
UnityEngine.Object component = GetComponent(itemIdx);
if (component != null)
fi.SetValue(ui, component);
else
Debug.LogErrorFormat("Component {0} is null", objs.name);
}
}
private void BindSubUI(IBindableUI ui, FieldInfo fi)
{
int subUIIdx = GetSubUIIndex(fi.Name);
if(subUIIdx == -1)
{
Debug.LogErrorFormat("can not find binding subUI of name [{0}] in prefab", fi.Name);
return;
}
fi.SetValue(ui, subUIItemDatas[subUIIdx].subUIData);
}
/// <summary>
/// 获取指定UI类的字段信息
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static UIFieldsInfo GetUIFieldsInfo(Type type)
{
UIFieldsInfo uIFieldsInfo;
if (s_uiFieldsCache.TryGetValue(type, out uIFieldsInfo))
return uIFieldsInfo;
uIFieldsInfo = new UIFieldsInfo() { type = type };
FieldInfo[] fis = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
for(int i = 0, imax = fis.Length; i < imax; i++)
{
FieldInfo fi = fis[i];
if (fi.IsDefined(typeof(ControlBindingAttribute), false))
uIFieldsInfo.controls.Add(fi);
else if (fi.IsDefined(typeof(SubUIBindingAttribute), false))
uIFieldsInfo.subUIs.Add(fi);
}
s_uiFieldsCache.Add(type, uIFieldsInfo);
return uIFieldsInfo;
}
#endregion
#region UnBind
private static List<UIControlData> s_tmpControlDataForUnbind = new List<UIControlData>();
/// <summary>
/// 解除指定UI及其子节点自动绑定字段的引用
/// </summary>
/// <param name="uiGo"></param>
public static void UnBindUI(GameObject uiGo)
{
if (uiGo == null)
return;
#if DEBUG_LOG
float time = Time.realtimeSinceStartup;
Profiler.BeginSample("UnBindUI");
#endif
uiGo.GetComponentsInChildren(true, s_tmpControlDataForUnbind);
for (int i = 0, imax = s_tmpControlDataForUnbind.Count; i < imax; i++)
{
UIControlData controlData = s_tmpControlDataForUnbind[i];
if (controlData.bindUIRefs == null)
continue;
List<WeakReference<IBindableUI>> bindUIRefs = controlData.bindUIRefs;
for (int j = 0, jmax = bindUIRefs.Count; j < jmax; j++)
{
WeakReference<IBindableUI> bindUIRef = bindUIRefs[j];
IBindableUI bindUI;
if (!bindUIRef.TryGetTarget(out bindUI))
continue;
}
controlData.bindUIRefs = null;
}
s_tmpControlDataForUnbind.Clear();
#if DEBUG_LOG
Profiler.EndSample();
float span = Time.realtimeSinceStartup - time;
if (span > 0.002f)
Debug.LogWarningFormat("UnBindUI {0} 耗时{1}ms", uiGo.name, span * 1000f);
#endif
}
#endregion
#region Get,使
/// <summary>
/// 找到指定名称的第一个组件, 不存在返回 null
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
public T GetComponent<T>(string name) where T : Component
{
int idx = GetCtrlIndex(name);
if (idx == -1)
return null;
var targets = ctrlItemDatas[idx].targets;
if (targets.Length == 0)
return null;
return targets[0] as T;
}
public new UnityEngine.Object GetComponent(string name)
{
int idx = GetCtrlIndex(name);
if (idx == -1)
return null;
var targets = ctrlItemDatas[idx].targets;
if (targets.Length == 0)
return null;
return targets[0];
}
public UnityEngine.Object GetComponent(int idx)
{
if (idx == -1 || idx >= ctrlItemDatas.Count)
return null;
var targets = ctrlItemDatas[idx].targets;
if (targets.Length == 0)
return null;
return targets[0];
}
public UnityEngine.Object[] GetComponents(string name)
{
int idx = GetCtrlIndex(name);
if (idx == -1)
return null;
return ctrlItemDatas[idx].targets;
}
public UnityEngine.Object[] GetComponents(int idx)
{
if (idx == -1 || idx >= ctrlItemDatas.Count)
return null;
return ctrlItemDatas[idx].targets;
}
private int GetCtrlIndex(string name)
{
for (int i = 0, imax = ctrlItemDatas.Count; i < imax; i++)
{
CtrlItemData item = ctrlItemDatas[i];
if (item.name == name)
return i;
}
return -1;
}
private int GetSubUIIndex(string name)
{
for(int i = 0, imax = subUIItemDatas.Count; i < imax; i++)
{
SubUIItemData item = subUIItemDatas[i];
if (item.name == name)
return i;
}
return -1;
}
#endregion
#region For Editor
#if UNITY_EDITOR
public bool dataHasChanged = false;
public bool CorrectComponents()
{
if (ctrlItemDatas == null) return true;
bool isOK = true;
for(int i = 0, imax = ctrlItemDatas.Count; i < imax; i++)
{
if (string.IsNullOrEmpty(ctrlItemDatas[i].name)) // TODO Check if is a valid varible name
{
Debug.LogErrorFormat("[{1}]第 {0} 个控件没有名字,请修正", i + 1, gameObject.name);
return false;
}
for (int j = ctrlItemDatas.Count - 1; j >= 0; j--)
{
if(ctrlItemDatas[i].name == ctrlItemDatas[j].name && i != j)
{
Debug.LogErrorFormat("[{3}]控件名字 [{0}] 第 {1} 项与第 {2} 项重复,请修正", ctrlItemDatas[i].name, i + 1, j + 1, gameObject.name);
return false;
}
}
}
isOK = ReplaceTargetsToUIComponent();
if(isOK)
Debug.LogFormat("[{0}]控件绑定修正完毕", gameObject.name);
return isOK;
}
public bool CheckSubUIs()
{
for (int i = 0, imax = subUIItemDatas.Count; i < imax; i++)
{
var subUI = subUIItemDatas[i];
if(subUI != null)
{
if (string.IsNullOrEmpty(subUI.name))
{
Debug.LogErrorFormat("[{0}]第 {1} 个子UI没有设置名字, 请修正", gameObject.name, i + 1);
return false;
}
if(subUI.subUIData == null)
{
Debug.LogErrorFormat("[{0}]第 {1} 个子UI没有赋值, 请修正", gameObject.name, i + 1);
return false;
}
// 必须拖当前 Prefab 下的子UI
if (!IsInCurrentPrefab(subUI.subUIData.transform))
{
Debug.LogErrorFormat("[{0}]第 {1} 个子UI [{2}]不是当前 Prefab 下的对象,请修正", gameObject.name, i + 1, subUI.name);
return false;
}
}
else
{
Debug.LogError("internal error at ControlBinding, pls contact author");
return false;
}
}
return true;
}
/// <summary>
/// 由于自动拖上去的对象永远都是 GameObject所以我们需要把它修正为正确的对象类型
/// </summary>
private bool ReplaceTargetsToUIComponent()
{
for (int i = 0, imax = ctrlItemDatas.Count; i < imax; i++)
{
var objs = ctrlItemDatas[i].targets;
Type type = null;
for(int j = 0, jmax = objs.Length; j < jmax; j++)
{
if(objs[j] == null)
{
Debug.LogErrorFormat("[{2}]控件名字 [{0}] 第 {1} 项为空,请修正", ctrlItemDatas[i].name, j + 1, gameObject.name);
return false;
}
GameObject go = objs[j] as GameObject;
if (go == null)
go = (objs[j] as Component).gameObject;
// 必须拖当前 Prefab 下的控件
if (!IsInCurrentPrefab(go.transform))
{
Debug.LogErrorFormat("[{2}]控件名字 [{0}] 第 {1} 项不是当前 Prefab 下的控件,请修正", ctrlItemDatas[i].name, j + 1, gameObject.name);
return false;
}
UnityEngine.Object correctComponent = FindCorrectComponent(go, ctrlItemDatas[i].type);
if(correctComponent == null)
{
Debug.LogErrorFormat("[{3}]控件 [{0}] 第 {1} 项不是 {2} 类型,请修正", ctrlItemDatas[i].name, j + 1, ctrlItemDatas[i].type, gameObject.name);
return false;
}
if (type == null) // 当前变量的第一个控件时执行
{
if (string.IsNullOrEmpty(ctrlItemDatas[i].type))
{
type = correctComponent.GetType();
}else
{
if(!_typeMap.TryGetValue(ctrlItemDatas[i].type, out type))
{
Debug.LogError("Internal Error, pls contact author");
return false;
}
}
}
else if(correctComponent.GetType() != type && !correctComponent.GetType().IsSubclassOf(type))
{
Debug.LogErrorFormat("[{2}]控件名字 [{0}] 第 {1} 项与第 1 项的类型不同,请修正", ctrlItemDatas[i].name, j + 1, gameObject.name);
return false;
}
if (objs[j] != correctComponent)
dataHasChanged = true;
objs[j] = correctComponent;
}
if(type.Name != ctrlItemDatas[i].type)
{
ctrlItemDatas[i].type = type.Name;
//#if UNITY_2019_1_OR_NEWER
// EditorUtility.ClearDirty(this);
//#endif
EditorUtility.SetDirty(this);
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
}
ctrlItemDatas[i].type = type.Name;
}
return true;
}
private bool IsInCurrentPrefab(Transform t)
{
do
{
if (t == transform)
return true;
t = t.parent;
} while (t != null);
return false;
}
private UnityEngine.Object FindCorrectComponent(GameObject go, string typename)
{
if (typename == "GameObject")
return go;
List<Component> components = new List<Component>();
go.GetComponents(components);
Func<Type, Component> getSpecialTypeComp = (Type t) =>
{
foreach (var comp in components)
{
Type compType = comp.GetType();
if (compType == t || compType.IsSubclassOf(t))
{
return comp;
}
}
return null;
};
Component newComp = null;
if (string.IsNullOrEmpty(typename))
{
// 类型名为空则为自动类型,在 _typeMap 里从上往下找
foreach (var kv in _typeMap)
{
newComp = getSpecialTypeComp(kv.Value);
if (newComp != null)
break;
}
}
else
{// 指定了类型名则只找指定类型的控件
Type type = null;
if (_typeMap.TryGetValue(typename, out type))
{
newComp = getSpecialTypeComp(type);
}
}
return newComp;
}
private bool IsNeedSave()
{
foreach(var ctrl in ctrlItemDatas)
{
if (string.IsNullOrEmpty(ctrl.type))
return true;
}
return false;
}
[ContextMenu("复制代码到剪贴板(Private)")]
public void CopyCodeToClipBoardPrivate()
{
CopyCodeToClipBoardImpl("private");
}
[ContextMenu("复制代码到剪贴板(Protected)")]
public void CopyCodeToClipBoardProtected()
{
CopyCodeToClipBoardImpl("protected");
}
[ContextMenu("复制代码到剪贴板(Public)")]
public void CopyCodeToClipBoardPublic()
{
CopyCodeToClipBoardImpl("public");
}
private void CopyCodeToClipBoardImpl(string accessLevel)
{
// 调用保存资源会导致 prefab 发生变化,因此只有有需要时才保存
if (IsNeedSave())
UIBindingPrefabSaveHelper.SavePrefab(gameObject);
StringBuilder sb = new StringBuilder(1024);
sb.AppendLine("#region 控件绑定变量声明,自动生成请勿手改");
sb.AppendLine("\t\t#pragma warning disable 0649"); // 变量未赋值
foreach (var ctrl in ctrlItemDatas)
{
if (ctrl.targets.Length == 0)
continue;
if (ctrl.targets.Length == 1)
sb.AppendFormat("\t\t[ControlBinding]\r\n\t\t{0} {1} {2};\r\n", accessLevel, ctrl.type, ctrl.name);
else
sb.AppendFormat("\t\t[ControlBinding]\r\n\t\t{0} {1}[] {2};\r\n", accessLevel, ctrl.type, ctrl.name);
}
sb.AppendLine();
foreach(var subUI in subUIItemDatas)
{
sb.AppendFormat("\t\t[SubUIBinding]\r\n\t\t{0} UIControlData {1};\r\n", accessLevel, subUI.name);
}
sb.AppendLine("\t\t#pragma warning restore 0649");
sb.Append("#endregion\r\n\r\n");
UnityEngine.GUIUtility.systemCopyBuffer = sb.ToString();
}
[ContextMenu("复制代码到剪贴板(Lua)")]
public void CopyCodeToClipBoardLua()
{
// 调用保存资源会导致 prefab 发生变化,因此只有有需要时才保存
if (IsNeedSave())
UIBindingPrefabSaveHelper.SavePrefab(gameObject);
StringBuilder sb = new StringBuilder(1024);
sb.Append("-- 控件绑定变量声明,自动生成请勿手改\r\n");
foreach (var ctrl in ctrlItemDatas)
{
if (ctrl.targets.Length == 0)
continue;
sb.AppendFormat("local {0}\r\n", ctrl.name);
}
sb.AppendFormat("\r\n");
sb.AppendFormat("-- SubUI\r\n");
foreach (var subUI in subUIItemDatas)
{
sb.AppendFormat("local {0}\r\n", subUI.name);
}
sb.Append("-- 控件绑定定义结束\r\n\r\n");
UnityEngine.GUIUtility.systemCopyBuffer = sb.ToString();
}
public void SetDirty()
{
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(gameObject);
#if UNITY_2021_1_OR_NEWER
var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(gameObject);
#else
var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(gameObject);
#endif
if (prefabStage != null)
{
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(prefabStage.scene);
}
#endif
}
#endif
#endregion
}
}