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;
|
||
|
}
|
||
|
}
|
||
|
}
|