ObjectReferenceField.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using UnityEngine;
  5. using UnityEngine.Timeline;
  6. using UnityEditor;
  7. using UnityEditor.SceneManagement;
  8. using UnityEngine.Playables;
  9. using Object = UnityEngine.Object;
  10. namespace UnityEditor.Timeline
  11. {
  12. // Describes the object references on a ScriptableObject, ignoring script fields
  13. struct ObjectReferenceField
  14. {
  15. public string propertyPath;
  16. public bool isSceneReference;
  17. public System.Type type;
  18. private readonly static ObjectReferenceField[] None = new ObjectReferenceField[0];
  19. private readonly static Dictionary<System.Type, ObjectReferenceField[]> s_Cache = new Dictionary<System.Type, ObjectReferenceField[]>();
  20. public static ObjectReferenceField[] FindObjectReferences(System.Type type)
  21. {
  22. if (type == null)
  23. return None;
  24. if (type.IsAbstract || type.IsInterface)
  25. return None;
  26. if (!typeof(ScriptableObject).IsAssignableFrom(type))
  27. return None;
  28. ObjectReferenceField[] result = null;
  29. if (s_Cache.TryGetValue(type, out result))
  30. return result;
  31. result = SearchForFields(type);
  32. s_Cache[type] = result;
  33. return result;
  34. }
  35. public static ObjectReferenceField[] FindObjectReferences<T>() where T : ScriptableObject, new()
  36. {
  37. return FindObjectReferences(typeof(T));
  38. }
  39. private static ObjectReferenceField[] SearchForFields(System.Type t)
  40. {
  41. Object instance = ScriptableObject.CreateInstance(t);
  42. var list = new List<ObjectReferenceField>();
  43. var serializableObject = new SerializedObject(instance);
  44. var prop = serializableObject.GetIterator();
  45. bool enterChildren = true;
  46. while (prop.NextVisible(enterChildren))
  47. {
  48. enterChildren = true;
  49. var ppath = prop.propertyPath;
  50. if (ppath == "m_Script")
  51. {
  52. enterChildren = false;
  53. }
  54. else if (prop.propertyType == SerializedPropertyType.ObjectReference || prop.propertyType == SerializedPropertyType.ExposedReference)
  55. {
  56. enterChildren = false;
  57. var exposedType = GetTypeFromPath(t, prop.propertyPath);
  58. if (exposedType != null && typeof(Object).IsAssignableFrom(exposedType))
  59. {
  60. bool isSceneRef = prop.propertyType == SerializedPropertyType.ExposedReference;
  61. list.Add(
  62. new ObjectReferenceField() {propertyPath = prop.propertyPath, isSceneReference = isSceneRef, type = exposedType}
  63. );
  64. }
  65. }
  66. }
  67. Object.DestroyImmediate(instance);
  68. if (list.Count == 0)
  69. return None;
  70. return list.ToArray();
  71. }
  72. private static System.Type GetTypeFromPath(System.Type baseType, string path)
  73. {
  74. if (string.IsNullOrEmpty(path))
  75. return null;
  76. System.Type parentType = baseType;
  77. FieldInfo field = null;
  78. var pathTo = path.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries);
  79. var flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic |
  80. BindingFlags.Instance;
  81. foreach (string s in pathTo)
  82. {
  83. field = parentType.GetField(s, flags);
  84. while (field == null)
  85. {
  86. if (parentType.BaseType == null)
  87. return null; // Should not happen really. Means SerializedObject got the property, but the reflection missed it
  88. parentType = parentType.BaseType;
  89. field = parentType.GetField(s, flags);
  90. }
  91. parentType = field.FieldType;
  92. }
  93. // dig out exposed reference types
  94. if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ExposedReference<Object>).GetGenericTypeDefinition())
  95. {
  96. return field.FieldType.GetGenericArguments()[0];
  97. }
  98. return field.FieldType;
  99. }
  100. public Object Find(ScriptableObject sourceObject, Object context = null)
  101. {
  102. if (sourceObject == null)
  103. return null;
  104. SerializedObject obj = new SerializedObject(sourceObject, context);
  105. var prop = obj.FindProperty(propertyPath);
  106. if (prop == null)
  107. throw new InvalidOperationException("sourceObject is not of the proper type. It does not contain a path to " + propertyPath);
  108. Object result = null;
  109. if (isSceneReference)
  110. {
  111. if (prop.propertyType != SerializedPropertyType.ExposedReference)
  112. throw new InvalidOperationException(propertyPath + " is marked as a Scene Reference, but is not an exposed reference type");
  113. if (context == null)
  114. Debug.LogWarning("ObjectReferenceField.Find " + " is called on a scene reference without a context, will always be null");
  115. result = prop.exposedReferenceValue;
  116. }
  117. else
  118. {
  119. if (prop.propertyType != SerializedPropertyType.ObjectReference)
  120. throw new InvalidOperationException(propertyPath + "is marked as an asset reference, but is not an object reference type");
  121. result = prop.objectReferenceValue;
  122. }
  123. return result;
  124. }
  125. /// <summary>
  126. /// Check if an Object satisfies this field, including components
  127. /// </summary>
  128. public bool IsAssignable(Object obj)
  129. {
  130. if (obj == null)
  131. return false;
  132. // types match
  133. bool potentialMatch = type.IsAssignableFrom(obj.GetType());
  134. // field is component, and it exists on the gameObject
  135. if (!potentialMatch && typeof(Component).IsAssignableFrom(type) && obj is GameObject)
  136. potentialMatch = ((GameObject)obj).GetComponent(type) != null;
  137. return potentialMatch && isSceneReference == obj.IsSceneObject();
  138. }
  139. /// <summary>
  140. /// Assigns a value to the field
  141. /// </summary>
  142. public bool Assign(ScriptableObject scriptableObject, Object value, IExposedPropertyTable exposedTable = null)
  143. {
  144. var serializedObject = new SerializedObject(scriptableObject, exposedTable as Object);
  145. var property = serializedObject.FindProperty(propertyPath);
  146. if (property == null)
  147. return false;
  148. // if the value is a game object, but the field is a component
  149. if (value is GameObject && typeof(Component).IsAssignableFrom(type))
  150. value = ((GameObject)value).GetComponent(type);
  151. if (isSceneReference)
  152. {
  153. property.exposedReferenceValue = value;
  154. // the object gets dirtied, but not the scene which is where the reference is stored
  155. var component = exposedTable as Component;
  156. if (component != null && !EditorApplication.isPlaying)
  157. EditorSceneManager.MarkSceneDirty(component.gameObject.scene);
  158. }
  159. else
  160. property.objectReferenceValue = value;
  161. serializedObject.ApplyModifiedProperties();
  162. return true;
  163. }
  164. }
  165. }