forked from Shardstone/trail-into-darkness
added more utilty packges
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using Debug = UnityEngine.Debug;
|
||||
using Object = UnityEngine.Object;
|
||||
using Type = System.Type;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Jovian.Utilities.Editor {
|
||||
/// <summary>
|
||||
/// Helper class for serializing objects in the editor
|
||||
/// </summary>
|
||||
public static class EditorSerializationUtility {
|
||||
|
||||
/// <summary>
|
||||
/// Returns the property type for cases when it is needed, like in the case of trying to get the type of SerializedPropertyType.ObjectReference
|
||||
/// Solution found on https://answers.unity.com/questions/929293/get-field-type-of-serializedproperty.html
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
public static Type GetTypeFromProperty(SerializedProperty property) {
|
||||
//gets parent type info
|
||||
string[] slices = property.propertyPath.Split('.');
|
||||
Type type = property.serializedObject.targetObject.GetType();
|
||||
|
||||
for (int i = 0; i < slices.Length; i++) {
|
||||
string slice = slices[i];
|
||||
if (slice == "Array") {
|
||||
i++; //skips "data[x]"
|
||||
if (type.IsArray) // e.g Type[]
|
||||
{
|
||||
type = type.GetElementType();
|
||||
}
|
||||
else if (type.IsGenericType) // e.g. List<Type>
|
||||
{
|
||||
type = type.GetGenericArguments()[0];
|
||||
}
|
||||
else {
|
||||
throw new NotSupportedException("Unsupported array type. Type[] or List<Type> are only supported array types");
|
||||
}
|
||||
}
|
||||
else {
|
||||
//gets info on field and its type
|
||||
type = type.GetField(slice, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance)?.FieldType;
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
throw new NullReferenceException(
|
||||
$"Type is null, something is not working correctly. Path={property.propertyPath}, Slice={slice}");
|
||||
}
|
||||
}
|
||||
|
||||
//type is now the type of the property
|
||||
return type;
|
||||
}
|
||||
|
||||
private static readonly Regex isArrayElementRegex = new Regex("Array.data\\[\\d+\\]$");
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the property is an array element
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsPropertyAnArrayElement(SerializedProperty property) {
|
||||
return isArrayElementRegex.IsMatch(property.propertyPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the parent array property for the given property, which is a member of the array
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
public static SerializedProperty GetArrayPropertyWithElementProperty(SerializedProperty property) {
|
||||
SerializedObject serializedObject = property.serializedObject;
|
||||
string propertyPath = property.propertyPath;
|
||||
int arrayDataIndex = propertyPath.LastIndexOf(".Array.data", StringComparison.Ordinal);
|
||||
string propertyPathWithoutArray = propertyPath.Substring(0, arrayDataIndex);
|
||||
int pathDividerIndex = propertyPathWithoutArray.LastIndexOf(".", StringComparison.Ordinal);
|
||||
|
||||
string parentPropertyName = propertyPathWithoutArray;
|
||||
if (pathDividerIndex != -1) {
|
||||
parentPropertyName = propertyPathWithoutArray.Substring(pathDividerIndex);
|
||||
}
|
||||
|
||||
return serializedObject.FindProperty(parentPropertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will save specifically the property args passed in the form new object[] { "propertyName1", "propertyName2", etc. } to the specified targetObject
|
||||
/// </summary>
|
||||
/// <param name="targetObject"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static void SaveObjectProperties(Object targetObject, params object[] args) {
|
||||
SerializedObject serializedObject = new SerializedObject(targetObject);
|
||||
|
||||
for (int i = 0; i < args.Length; i += 2) {
|
||||
object keyArg = args[i];
|
||||
Type keyType = keyArg.GetType();
|
||||
|
||||
if ((keyType == typeof(string)) == false) {
|
||||
throw new NotSupportedException($"Key must be string. {args[i]} is {keyType}");
|
||||
}
|
||||
|
||||
SerializedProperty property = serializedObject.FindProperty((string)keyArg);
|
||||
object argValue = args[i + 1];
|
||||
|
||||
if (property == null) {
|
||||
throw new Exception($"No property found for key {keyArg}");
|
||||
}
|
||||
|
||||
if (argValue == null) {
|
||||
property.objectReferenceValue = null;
|
||||
}
|
||||
else {
|
||||
SetSerializedPropertyValue(property, argValue);
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will auto-detect the value type and save it to the property. It only supports the serializable types that the Unity serialization system supports
|
||||
/// </summary>
|
||||
/// <param name="fromProperty"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
public static void SetSerializedPropertyValue(SerializedProperty fromProperty, object value) {
|
||||
if (fromProperty == null) {
|
||||
Debug.LogError("SetSerializedPropertyValue failed, property is null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Strings are counted as arrays but should be handled separately
|
||||
if (fromProperty.isArray && fromProperty.propertyType != SerializedPropertyType.String) {
|
||||
fromProperty.arraySize = 0;
|
||||
var argArray = (IEnumerable)value;
|
||||
var enumerator = argArray.GetEnumerator();
|
||||
|
||||
int index = 0;
|
||||
while (enumerator.MoveNext()) {
|
||||
fromProperty.InsertArrayElementAtIndex(index);
|
||||
var arrayProperty = fromProperty.GetArrayElementAtIndex(index);
|
||||
SetSerializedPropertyValue(arrayProperty, enumerator.Current);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (fromProperty.propertyType) {
|
||||
case SerializedPropertyType.AnimationCurve:
|
||||
fromProperty.animationCurveValue = (AnimationCurve)value;
|
||||
break;
|
||||
case SerializedPropertyType.Boolean:
|
||||
fromProperty.boolValue = (bool)value;
|
||||
break;
|
||||
case SerializedPropertyType.BoundsInt:
|
||||
fromProperty.boundsIntValue = (BoundsInt)value;
|
||||
break;
|
||||
case SerializedPropertyType.Character:
|
||||
fromProperty.intValue = (int)(char)value;
|
||||
break;
|
||||
case SerializedPropertyType.Color: {
|
||||
if (value is Color32 color32) {
|
||||
fromProperty.colorValue = (Color)color32;
|
||||
}
|
||||
else {
|
||||
fromProperty.colorValue = (Color)value;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SerializedPropertyType.ExposedReference:
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
fromProperty.objectReferenceValue = (Object)value;
|
||||
break;
|
||||
case SerializedPropertyType.Float:
|
||||
fromProperty.floatValue = (float)value;
|
||||
break;
|
||||
case SerializedPropertyType.Integer:
|
||||
fromProperty.intValue = (int)value;
|
||||
break;
|
||||
case SerializedPropertyType.LayerMask:
|
||||
fromProperty.intValue = ((LayerMask)value).value;
|
||||
break;
|
||||
case SerializedPropertyType.Quaternion:
|
||||
fromProperty.quaternionValue = (Quaternion)value;
|
||||
break;
|
||||
case SerializedPropertyType.Rect:
|
||||
fromProperty.rectValue = (Rect)value;
|
||||
break;
|
||||
case SerializedPropertyType.RectInt:
|
||||
fromProperty.rectIntValue = (RectInt)value;
|
||||
break;
|
||||
case SerializedPropertyType.String:
|
||||
fromProperty.stringValue = (string)value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector2:
|
||||
fromProperty.vector2Value = (Vector2)value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector2Int:
|
||||
fromProperty.vector2IntValue = (Vector2Int)value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector3:
|
||||
fromProperty.vector3Value = (Vector3)value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector3Int:
|
||||
fromProperty.vector3IntValue = (Vector3Int)value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector4:
|
||||
fromProperty.vector4Value = (Vector4)value;
|
||||
break;
|
||||
case SerializedPropertyType.Enum:
|
||||
fromProperty.enumValueIndex = Array.IndexOf(Enum.GetValues(value.GetType()), value);
|
||||
// flags???
|
||||
break;
|
||||
case SerializedPropertyType.Generic:
|
||||
SaveGenericProperty(fromProperty, value);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"PropertyType {fromProperty.propertyType} is not supported - yet. Array? {fromProperty.isArray} Path: {fromProperty.propertyPath}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will copy fromProperty to toProperty. It only supports the serializable types that the Unity serialization system supports
|
||||
/// </summary>
|
||||
/// <param name="fromProperty"></param>
|
||||
/// <param name="toProperty"></param>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
public static void CopyPropertyValue(SerializedProperty fromProperty, SerializedProperty toProperty) {
|
||||
Assert.IsNotNull(fromProperty, "fromProperty == null");
|
||||
Assert.IsNotNull(toProperty, "toProperty == null");
|
||||
Assert.AreEqual(fromProperty.propertyType, toProperty.propertyType, $"Properties do not match types. fromType={fromProperty.propertyType}, toType={toProperty.propertyType}");
|
||||
switch (fromProperty.propertyType) {
|
||||
case SerializedPropertyType.AnimationCurve:
|
||||
toProperty.animationCurveValue = fromProperty.animationCurveValue;
|
||||
break;
|
||||
case SerializedPropertyType.Boolean:
|
||||
toProperty.boolValue = fromProperty.boolValue;
|
||||
break;
|
||||
case SerializedPropertyType.BoundsInt:
|
||||
toProperty.boundsIntValue = fromProperty.boundsIntValue;
|
||||
break;
|
||||
case SerializedPropertyType.Character:
|
||||
toProperty.intValue = fromProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.Color: {
|
||||
toProperty.colorValue = fromProperty.colorValue;
|
||||
break;
|
||||
}
|
||||
case SerializedPropertyType.ExposedReference:
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
toProperty.objectReferenceValue = fromProperty.objectReferenceValue;
|
||||
break;
|
||||
case SerializedPropertyType.Float:
|
||||
toProperty.floatValue = fromProperty.floatValue;
|
||||
break;
|
||||
case SerializedPropertyType.Integer:
|
||||
toProperty.intValue = fromProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.LayerMask:
|
||||
toProperty.intValue = fromProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.Quaternion:
|
||||
toProperty.quaternionValue = fromProperty.quaternionValue;
|
||||
break;
|
||||
case SerializedPropertyType.Rect:
|
||||
toProperty.rectValue = fromProperty.rectValue;
|
||||
break;
|
||||
case SerializedPropertyType.RectInt:
|
||||
toProperty.rectIntValue = fromProperty.rectIntValue;
|
||||
break;
|
||||
case SerializedPropertyType.String:
|
||||
toProperty.stringValue = fromProperty.stringValue;
|
||||
break;
|
||||
case SerializedPropertyType.Vector2:
|
||||
toProperty.vector2Value = fromProperty.vector2Value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector2Int:
|
||||
toProperty.vector2IntValue = fromProperty.vector2IntValue;
|
||||
break;
|
||||
case SerializedPropertyType.Vector3:
|
||||
toProperty.vector3Value = fromProperty.vector3Value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector3Int:
|
||||
toProperty.vector3IntValue = fromProperty.vector3IntValue;
|
||||
break;
|
||||
case SerializedPropertyType.Vector4:
|
||||
toProperty.vector4Value = fromProperty.vector4Value;
|
||||
break;
|
||||
case SerializedPropertyType.Enum:
|
||||
toProperty.intValue = fromProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.Generic:
|
||||
CopyGenericProperty(fromProperty, toProperty);
|
||||
break;
|
||||
case SerializedPropertyType.ManagedReference:
|
||||
toProperty.managedReferenceValue = Activator.CreateInstance(fromProperty.managedReferenceValue.GetType());
|
||||
CopyGenericProperty(fromProperty, toProperty);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"PropertyType {fromProperty.propertyType} is not supported - yet. Array? {fromProperty.isArray} Path: {fromProperty.propertyPath}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyGenericProperty(SerializedProperty fromProperty, SerializedProperty toProperty) {
|
||||
IEnumerator fromPropertyEnumerator = fromProperty.GetEnumerator();
|
||||
IEnumerator toPropertyEnumerator = toProperty.GetEnumerator();
|
||||
while (toPropertyEnumerator.MoveNext() && fromPropertyEnumerator.MoveNext()) {
|
||||
if (toPropertyEnumerator.Current is SerializedProperty toChildProperty &&
|
||||
fromPropertyEnumerator.Current is SerializedProperty fromChildProperty) {
|
||||
CopyPropertyValue(fromChildProperty, toChildProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveGenericProperty(SerializedProperty property, object instance) {
|
||||
Type type = instance.GetType();
|
||||
IEnumerable<FieldInfo> fields = type.GetRuntimeFields();
|
||||
foreach (FieldInfo field in fields) {
|
||||
if (field.IsNotSerialized || field.IsStatic) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SerializedProperty fieldProperty = property.FindPropertyRelative(field.Name);
|
||||
if (fieldProperty != null) {
|
||||
SetSerializedPropertyValue(fieldProperty, field.GetValue(instance));
|
||||
}
|
||||
else {
|
||||
Debug.Log($"SaveGenericProperty cannot field serializedProperty named '{field.Name}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetAttribute<TAttribute>(
|
||||
this SerializedProperty serializedProperty,
|
||||
out TAttribute attribute,
|
||||
bool includeAttributesFromParentProperties = false,
|
||||
bool includeInheritedAttributes = true
|
||||
)
|
||||
where TAttribute : Attribute {
|
||||
if (serializedProperty == null) {
|
||||
throw new ArgumentNullException(nameof(serializedProperty));
|
||||
}
|
||||
|
||||
Type targetObjectType = serializedProperty.serializedObject.targetObject.GetType();
|
||||
|
||||
if (targetObjectType == null) {
|
||||
throw new ArgumentException($"Could not find the {nameof(targetObjectType)} of {nameof(serializedProperty)}");
|
||||
}
|
||||
|
||||
string[] pathSegments = includeAttributesFromParentProperties ? serializedProperty.propertyPath.Split('.') : new[] { serializedProperty.propertyPath };
|
||||
|
||||
foreach (string pathSegment in pathSegments) {
|
||||
FieldInfo fieldInfo = targetObjectType.GetField(pathSegment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
|
||||
if (fieldInfo != null) {
|
||||
attribute = fieldInfo.GetCustomAttribute<TAttribute>(includeInheritedAttributes);
|
||||
if (attribute != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PropertyInfo propertyInfo = targetObjectType.GetProperty(pathSegment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
|
||||
if (propertyInfo != null) {
|
||||
attribute = propertyInfo.GetCustomAttribute<TAttribute>(includeInheritedAttributes);
|
||||
if (attribute != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attribute = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetAttributes<TAttribute>(
|
||||
SerializedProperty serializedProperty,
|
||||
out List<TAttribute> attributes,
|
||||
bool includeAttributesFromParentProperties = false,
|
||||
bool includeInheritedAttributes = true
|
||||
)
|
||||
where TAttribute : Attribute {
|
||||
if (serializedProperty == null) {
|
||||
throw new ArgumentNullException(nameof(serializedProperty));
|
||||
}
|
||||
|
||||
Type targetObjectType = serializedProperty.serializedObject.targetObject.GetType();
|
||||
|
||||
if (targetObjectType == null) {
|
||||
throw new ArgumentException($"Could not find the {nameof(targetObjectType)} of {nameof(serializedProperty)}");
|
||||
}
|
||||
|
||||
attributes = new List<TAttribute>();
|
||||
|
||||
string[] pathSegments = includeAttributesFromParentProperties ? serializedProperty.propertyPath.Split('.') : new[] { serializedProperty.propertyPath };
|
||||
|
||||
foreach (string pathSegment in pathSegments) {
|
||||
FieldInfo fieldInfo = targetObjectType.GetField(pathSegment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
|
||||
if (fieldInfo != null) {
|
||||
IEnumerable<TAttribute> foundAttributes = fieldInfo.GetCustomAttributes<TAttribute>(includeInheritedAttributes);
|
||||
foreach (TAttribute foundAttribute in foundAttributes) {
|
||||
attributes.Add(foundAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
PropertyInfo propertyInfo = targetObjectType.GetProperty(pathSegment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
|
||||
if (propertyInfo != null) {
|
||||
IEnumerable<TAttribute> foundAttributes = propertyInfo.GetCustomAttributes<TAttribute>(includeInheritedAttributes);
|
||||
foreach (TAttribute attribute in foundAttributes) {
|
||||
attributes.Add(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attributes.Count > 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13a6cb5c52b6ad744a90547f102f5296
|
||||
Reference in New Issue
Block a user