196 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			196 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Reflection;
 | |
| using UnityEngine;
 | |
| using UnityEngine.Timeline;
 | |
| using UnityEditor;
 | |
| using UnityEditor.SceneManagement;
 | |
| using UnityEngine.Playables;
 | |
| using Object = UnityEngine.Object;
 | |
| 
 | |
| namespace UnityEditor.Timeline
 | |
| {
 | |
|     // Describes the object references on a ScriptableObject, ignoring script fields
 | |
|     struct ObjectReferenceField
 | |
|     {
 | |
|         public string propertyPath;
 | |
|         public bool isSceneReference;
 | |
|         public System.Type type;
 | |
| 
 | |
|         private readonly static ObjectReferenceField[] None = new ObjectReferenceField[0];
 | |
|         private readonly static Dictionary<System.Type, ObjectReferenceField[]> s_Cache = new Dictionary<System.Type, ObjectReferenceField[]>();
 | |
| 
 | |
|         public static ObjectReferenceField[] FindObjectReferences(System.Type type)
 | |
|         {
 | |
|             if (type == null)
 | |
|                 return None;
 | |
| 
 | |
|             if (type.IsAbstract || type.IsInterface)
 | |
|                 return None;
 | |
| 
 | |
|             if (!typeof(ScriptableObject).IsAssignableFrom(type))
 | |
|                 return None;
 | |
| 
 | |
|             ObjectReferenceField[] result = null;
 | |
|             if (s_Cache.TryGetValue(type, out result))
 | |
|                 return result;
 | |
| 
 | |
|             result = SearchForFields(type);
 | |
|             s_Cache[type] = result;
 | |
|             return result;
 | |
|         }
 | |
| 
 | |
|         public static ObjectReferenceField[] FindObjectReferences<T>() where T : ScriptableObject, new()
 | |
|         {
 | |
|             return FindObjectReferences(typeof(T));
 | |
|         }
 | |
| 
 | |
|         private static ObjectReferenceField[] SearchForFields(System.Type t)
 | |
|         {
 | |
|             Object instance = ScriptableObject.CreateInstance(t);
 | |
|             var list = new List<ObjectReferenceField>();
 | |
| 
 | |
|             var serializableObject = new SerializedObject(instance);
 | |
|             var prop = serializableObject.GetIterator();
 | |
|             bool enterChildren = true;
 | |
|             while (prop.NextVisible(enterChildren))
 | |
|             {
 | |
|                 enterChildren = true;
 | |
|                 var ppath = prop.propertyPath;
 | |
|                 if (ppath == "m_Script")
 | |
|                 {
 | |
|                     enterChildren = false;
 | |
|                 }
 | |
|                 else if (prop.propertyType == SerializedPropertyType.ObjectReference || prop.propertyType == SerializedPropertyType.ExposedReference)
 | |
|                 {
 | |
|                     enterChildren = false;
 | |
|                     var exposedType = GetTypeFromPath(t, prop.propertyPath);
 | |
|                     if (exposedType != null && typeof(Object).IsAssignableFrom(exposedType))
 | |
|                     {
 | |
|                         bool isSceneRef = prop.propertyType == SerializedPropertyType.ExposedReference;
 | |
|                         list.Add(
 | |
|                             new ObjectReferenceField() { propertyPath = prop.propertyPath, isSceneReference = isSceneRef, type = exposedType }
 | |
|                         );
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             Object.DestroyImmediate(instance);
 | |
|             if (list.Count == 0)
 | |
|                 return None;
 | |
|             return list.ToArray();
 | |
|         }
 | |
| 
 | |
|         private static System.Type GetTypeFromPath(System.Type baseType, string path)
 | |
|         {
 | |
|             if (string.IsNullOrEmpty(path))
 | |
|                 return null;
 | |
| 
 | |
|             System.Type parentType = baseType;
 | |
|             FieldInfo field = null;
 | |
|             var pathTo = path.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
 | |
|             var flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic |
 | |
|                 BindingFlags.Instance;
 | |
|             foreach (string s in pathTo)
 | |
|             {
 | |
|                 field = parentType.GetField(s, flags);
 | |
|                 while (field == null)
 | |
|                 {
 | |
|                     if (parentType.BaseType == null)
 | |
|                         return null; // Should not happen really. Means SerializedObject got the property, but the reflection missed it
 | |
|                     parentType = parentType.BaseType;
 | |
|                     field = parentType.GetField(s, flags);
 | |
|                 }
 | |
| 
 | |
|                 parentType = field.FieldType;
 | |
|             }
 | |
| 
 | |
|             // dig out exposed reference types
 | |
|             if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ExposedReference<Object>).GetGenericTypeDefinition())
 | |
|             {
 | |
|                 return field.FieldType.GetGenericArguments()[0];
 | |
|             }
 | |
| 
 | |
|             return field.FieldType;
 | |
|         }
 | |
| 
 | |
|         public Object Find(ScriptableObject sourceObject, Object context = null)
 | |
|         {
 | |
|             if (sourceObject == null)
 | |
|                 return null;
 | |
| 
 | |
|             SerializedObject obj = new SerializedObject(sourceObject, context);
 | |
|             var prop = obj.FindProperty(propertyPath);
 | |
|             if (prop == null)
 | |
|                 throw new InvalidOperationException("sourceObject is not of the proper type. It does not contain a path to " + propertyPath);
 | |
| 
 | |
|             Object result = null;
 | |
|             if (isSceneReference)
 | |
|             {
 | |
|                 if (prop.propertyType != SerializedPropertyType.ExposedReference)
 | |
|                     throw new InvalidOperationException(propertyPath + " is marked as a Scene Reference, but is not an exposed reference type");
 | |
|                 if (context == null)
 | |
|                     Debug.LogWarning("ObjectReferenceField.Find " + " is called on a scene reference without a context, will always be null");
 | |
| 
 | |
|                 result = prop.exposedReferenceValue;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (prop.propertyType != SerializedPropertyType.ObjectReference)
 | |
|                     throw new InvalidOperationException(propertyPath + "is marked as an asset reference, but is not an object reference type");
 | |
|                 result = prop.objectReferenceValue;
 | |
|             }
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Check if an Object satisfies this field, including components
 | |
|         /// </summary>
 | |
|         public bool IsAssignable(Object obj)
 | |
|         {
 | |
|             if (obj == null)
 | |
|                 return false;
 | |
| 
 | |
|             // types match
 | |
|             bool potentialMatch = type.IsAssignableFrom(obj.GetType());
 | |
| 
 | |
|             // field is component, and it exists on the gameObject
 | |
|             if (!potentialMatch && typeof(Component).IsAssignableFrom(type) && obj is GameObject)
 | |
|                 potentialMatch = ((GameObject)obj).GetComponent(type) != null;
 | |
| 
 | |
|             return potentialMatch && isSceneReference == obj.IsSceneObject();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Assigns a value to the field
 | |
|         /// </summary>
 | |
|         public bool Assign(ScriptableObject scriptableObject, Object value, IExposedPropertyTable exposedTable = null)
 | |
|         {
 | |
|             var serializedObject = new SerializedObject(scriptableObject, exposedTable as Object);
 | |
|             var property = serializedObject.FindProperty(propertyPath);
 | |
|             if (property == null)
 | |
|                 return false;
 | |
| 
 | |
|             // if the value is a game object, but the field is a component
 | |
|             if (value is GameObject && typeof(Component).IsAssignableFrom(type))
 | |
|                 value = ((GameObject)value).GetComponent(type);
 | |
| 
 | |
|             if (isSceneReference)
 | |
|             {
 | |
|                 property.exposedReferenceValue = value;
 | |
| 
 | |
|                 // the object gets dirtied, but not the scene which is where the reference is stored
 | |
|                 var component = exposedTable as Component;
 | |
|                 if (component != null && !EditorApplication.isPlaying)
 | |
|                     EditorSceneManager.MarkSceneDirty(component.gameObject.scene);
 | |
|             }
 | |
|             else
 | |
|                 property.objectReferenceValue = value;
 | |
| 
 | |
|             serializedObject.ApplyModifiedProperties();
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| }
 |