Betterizer.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Runtime.InteropServices;
  6. using System.Text;
  7. using UnityEditor;
  8. using UnityEngine;
  9. namespace TheraBytes.BetterUi.Editor
  10. {
  11. public static class Betterizer
  12. {
  13. public static T MakeBetter<T, TBetter>(T source, params string[] skipFields)
  14. where T : MonoBehaviour
  15. where TBetter : T
  16. {
  17. string typeName = typeof(T).Name;
  18. // if (typeof(TBetter).GetInterfaces().Contains(typeof(IResolutionDependency)))
  19. // {
  20. // if (ResolutionMonitor.CurrentResolution != ResolutionMonitor.OptimizedResolution)
  21. // {
  22. // if (!(EditorUtility.DisplayDialog("Not working with Optimized resolution",
  23. //string.Format("Optimized Resolution: {0} x {1}{5}Current Resolution: {2} x {3}{5}"
  24. //+ "You should work with optimized resolution "
  25. //+ "whenever you make a {4} better to avoid unexpected sizes. {5}"
  26. //+ "Do you want to make it better anyway?",
  27. //ResolutionMonitor.OptimizedResolution.x, ResolutionMonitor.OptimizedResolution.y,
  28. //ResolutionMonitor.CurrentResolution.x, ResolutionMonitor.CurrentResolution.y,
  29. //typeName, Environment.NewLine),
  30. //"Yes, make it Better", "No, I will change the resolution.")))
  31. // {
  32. // return null;
  33. // }
  34. // }
  35. // }
  36. if (source is TBetter)
  37. {
  38. if (EditorUtility.DisplayDialog("Already Good Enough",
  39. string.Format("This already is a 'Better {0}'.{1}Do you want to downgrade it to '{0}'?",
  40. typeName, Environment.NewLine),
  41. "Yes", "No"))
  42. {
  43. return Convert<T, TBetter>(source, true, skipFields);
  44. }
  45. return null;
  46. }
  47. return Convert<T, TBetter>(source, false, skipFields);
  48. }
  49. static T Convert<T, TBetter>(T source, bool downgrade, params string[] skipFields)
  50. where T : MonoBehaviour
  51. where TBetter : T
  52. {
  53. var fields = CollectAllFieldValues(typeof(T), source, new HashSet<string>(skipFields)).ToArray();
  54. var refs = new HashSet<KeyValuePair<SerializedObject, string>>(FindReferencesTo(source));
  55. GameObject go = source.gameObject;
  56. int order = GetComponentOrder(source);
  57. Undo.SetCurrentGroupName(string.Format("Make Better {0}", DateTime.Now.ToFileTimeUtc()));
  58. Undo.DestroyObjectImmediate(source);
  59. T better = (downgrade)
  60. ? Undo.AddComponent<T>(go)
  61. : Undo.AddComponent<TBetter>(go);
  62. Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
  63. foreach (var kv in fields)
  64. {
  65. try
  66. {
  67. kv.Key.SetValue(better, kv.Value);
  68. }
  69. catch (Exception ex)
  70. {
  71. Debug.LogErrorFormat("Could not set value {0}: {1}", kv.Key.Name, ex.Message);
  72. }
  73. }
  74. foreach (var r in refs)
  75. {
  76. SetReference(r.Key, r.Value, better);
  77. }
  78. for (int i = 0; i < order; i++)
  79. {
  80. UnityEditorInternal.ComponentUtility.MoveComponentUp(better);
  81. }
  82. return better;
  83. }
  84. public static void Validate(UnityEngine.EventSystems.UIBehaviour obj)
  85. {
  86. if (obj == null)
  87. return;
  88. var t = obj.GetType();
  89. t.InvokeMember("OnValidate",
  90. BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
  91. null, obj, null);
  92. }
  93. static int GetComponentOrder(Component comp)
  94. {
  95. int idx = 0;
  96. while (UnityEditorInternal.ComponentUtility.MoveComponentDown(comp))
  97. {
  98. idx++;
  99. }
  100. return idx;
  101. }
  102. static IEnumerable<KeyValuePair<FieldInfo, object>> CollectAllFieldValues(Type type, object source,
  103. HashSet<string> skipFields)
  104. {
  105. foreach (var field in CollectFieldInfosRecursively(type))
  106. {
  107. // skip private fields which are not marked as SerializeField
  108. if (!(field.IsPublic) && (field.GetCustomAttributes(typeof(SerializeField), true).Length == 0))
  109. continue;
  110. // skip public fields which are marked as NonSerialized
  111. if ((field.IsPublic) && (field.GetCustomAttributes(typeof(NonSerializedAttribute), true).Length > 0))
  112. continue;
  113. // skip special fields which make problems copying them
  114. if (skipFields.Contains(field.Name))
  115. continue;
  116. // skip arrays for now...
  117. // find a solution to copy it if needed
  118. if (field.FieldType.IsArray)
  119. {
  120. Debug.LogWarningFormat("Collect Fields: Array '{0}' skipped", field.Name);
  121. continue;
  122. }
  123. object value = field.GetValue(source);
  124. value = CreateCopyIfNoUnityObject(value);
  125. yield return new KeyValuePair<FieldInfo, object>(field, value);
  126. }
  127. }
  128. static IEnumerable<FieldInfo> CollectFieldInfosRecursively(Type type)
  129. {
  130. while (type != typeof(object))
  131. {
  132. foreach (var field in CollectFieldInfos(type))
  133. {
  134. yield return field;
  135. }
  136. type = type.BaseType;
  137. }
  138. }
  139. static IEnumerable<FieldInfo> CollectFieldInfos(Type type)
  140. {
  141. FieldInfo[] myObjectFields = type.GetFields(
  142. BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  143. foreach (var field in myObjectFields)
  144. {
  145. yield return field;
  146. }
  147. }
  148. static object CreateCopyIfNoUnityObject(object original)
  149. {
  150. if (original is UnityEngine.Object || original == null)
  151. {
  152. return original;
  153. }
  154. else
  155. {
  156. Type type = original.GetType();
  157. object copy = (type == typeof(string))
  158. ? original as string
  159. : Activator.CreateInstance(type);
  160. CopyValuesRecursive(type, original, copy);
  161. return copy;
  162. }
  163. }
  164. public static void CopyValuesRecursive(Type type, object source, object target)
  165. {
  166. while (type != typeof(object))
  167. {
  168. CopyValues(type, source, target);
  169. type = type.BaseType;
  170. }
  171. }
  172. public static void CopyValues(Type type, object source, object target)
  173. {
  174. FieldInfo[] myObjectFields = type.GetFields(
  175. BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  176. foreach (FieldInfo fi in myObjectFields)
  177. {
  178. fi.SetValue(target, fi.GetValue(source));
  179. }
  180. }
  181. private static IEnumerable<KeyValuePair<SerializedObject, string>> FindReferencesTo(UnityEngine.Component obj)
  182. {
  183. // iterate objects in the scene
  184. var allObjects =
  185. #if UNITY_2022_2_OR_NEWER
  186. UnityEngine.Object.FindObjectsByType<GameObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
  187. #else
  188. UnityEngine.Object.FindObjectsOfType<GameObject>();
  189. #endif
  190. for (int i = 0; i < allObjects.Length; i++)
  191. {
  192. var go = allObjects[i];
  193. foreach (var keyValuePair in FindReferencesTo(obj, go))
  194. {
  195. yield return keyValuePair;
  196. }
  197. }
  198. // iterate object in this prefab
  199. foreach (Transform transform in IterateChildrenRecursively(obj.transform.root))
  200. {
  201. var go = transform.gameObject;
  202. foreach (var keyValuePair in FindReferencesTo(obj, go))
  203. {
  204. yield return keyValuePair;
  205. }
  206. }
  207. }
  208. private static IEnumerable<KeyValuePair<SerializedObject, string>> FindReferencesTo(Component obj, GameObject go)
  209. {
  210. var components = go.GetComponents<Component>();
  211. for (int k = 0; k < components.Length; k++)
  212. {
  213. var comp = components[k];
  214. if (comp == null || comp == obj)
  215. continue;
  216. var so = new SerializedObject(comp);
  217. var sp = so.GetIterator();
  218. while (sp.NextVisible(true))
  219. {
  220. if (sp.propertyType == SerializedPropertyType.ObjectReference
  221. && sp.objectReferenceValue == obj)
  222. {
  223. string path = sp.propertyPath;
  224. yield return new KeyValuePair<SerializedObject, string>(so, path);
  225. }
  226. }
  227. }
  228. }
  229. private static void SetReference(SerializedObject so, string path, UnityEngine.Object obj)
  230. {
  231. var prop = so.FindProperty(path);
  232. //Debug.LogWarningFormat("{0} ({1})", path, prop.propertyType);
  233. prop.objectReferenceValue = obj;
  234. so.ApplyModifiedProperties();
  235. }
  236. static IEnumerable<Transform> IterateChildrenRecursively(Transform self)
  237. {
  238. yield return self;
  239. for (int i = 0; i < self.childCount; i++)
  240. {
  241. Transform child = self.GetChild(i);
  242. foreach (Transform c in IterateChildrenRecursively(child))
  243. {
  244. yield return c;
  245. }
  246. }
  247. }
  248. }
  249. }