CapersProject/Assets/Plugins/Sirenix/Odin Inspector/Modules/Unity.Addressables/AddressablesInspectors.cs
2024-11-11 11:02:24 +09:00

1276 lines
48 KiB
C#

//-----------------------------------------------------------------------
// <copyright file="AddressablesInspectors.cs" company="Sirenix ApS">
// Copyright (c) Sirenix ApS. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using Sirenix.OdinInspector;
using UnityEngine;
[assembly: RegisterAssetReferenceAttributeForwardToChild(typeof(InlineEditorAttribute))]
[assembly: RegisterAssetReferenceAttributeForwardToChild(typeof(PreviewFieldAttribute))]
namespace Sirenix.OdinInspector
{
using System.Diagnostics;
/// <summary>
/// <para>DisallowAddressableSubAssetField is used on AssetReference properties, and disallows and prevents assigned sub-assets to the asset reference.</para>
/// </summary>
/// <example>
/// <code>
/// [DisallowAddressableSubAssetField]
/// public AssetReference Reference;
/// </code>
/// </example>
/// <seealso cref="RegisterAssetReferenceAttributeForwardToChildAttribute" />
/// <seealso cref="AssetReferenceUILabelRestriction" />
[Conditional("UNITY_EDITOR")]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Parameter)]
public class DisallowAddressableSubAssetFieldAttribute : Attribute
{
}
/// <summary>
/// <para>Registers an attribute to be applied to an AssetRefenece property, to be forwarded and applied to the AssetReference's child instead.</para>
/// <para>This allows attributes designed for use on UnityEngine.Objects to be used on AssetReference properties as well.</para>
/// <para>By default, <c>InlineEditorAttribute</c> and <c>PreviewFieldAttribute</c> are registered for forwarding.</para>
/// </summary>
/// <example>
/// <code>
/// [assembly: Sirenix.OdinInspector.Modules.RegisterAssetReferenceAttributeForwardToChild(typeof(InlineEditorAttribute))]
/// [assembly: Sirenix.OdinInspector.Modules.RegisterAssetReferenceAttributeForwardToChild(typeof(PreviewFieldAttribute))]
/// </code>
/// </example>
/// <seealso cref="DisallowAddressableSubAssetFieldAttribute" />
[Conditional("UNITY_EDITOR")]
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class RegisterAssetReferenceAttributeForwardToChildAttribute : Attribute // TODO: Should this be a global attribute?
{
/// <summary>
/// The type of the attribute to forward.
/// </summary>
public readonly Type AttributeType;
/// <summary>
/// Registers the specified attribute to be copied and applied to the AssetReference's UnityEngine.Object child instead.
/// </summary>
/// <param name="attributeType">The attribute type to forward.</param>
public RegisterAssetReferenceAttributeForwardToChildAttribute(Type attributeType)
{
this.AttributeType = attributeType;
}
}
}
#if UNITY_EDITOR
namespace Sirenix.OdinInspector.Modules.Addressables.Editor
{
using Sirenix.OdinInspector.Editor;
using Sirenix.Serialization;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.GUI;
using UnityEngine;
using UnityEngine.AddressableAssets;
using System.Runtime.Serialization;
using System.IO;
/// <summary>
/// Draws an AssetReference property.
/// </summary>
/// <typeparam name="T">The concrete type of AssetReference to be drawn. For example, <c>AssetReferenceTexture</c>.</typeparam>
[DrawerPriority(0, 1, 0)]
public class AssetReferenceDrawer<T> : OdinValueDrawer<T>, IDefinesGenericMenuItems
where T : AssetReference
{
private bool hideAssetReferenceField;
private Type targetType;
private string NoneSelectedLabel;
//private string[] labelRestrictions;
private bool disallowSubAssets;
private bool showSubAssetField;
private bool updateShowSubAssetField;
private List<AssetReferenceUIRestriction> restrictions;
protected override bool CanDrawValueProperty(InspectorProperty property)
{
return property.GetAttribute<DrawWithUnityAttribute>() == null;
}
protected override void Initialize()
{
// If a child exists, we draw that child instead of the AssetReference field.
if (this.Property.Children.Count > 0)
{
this.hideAssetReferenceField = true;
return;
}
this.targetType = OdinAddressableUtility.GetAssetReferenceTargetType(typeof(T));
if (this.targetType == typeof(UnityEngine.Object))
{
this.NoneSelectedLabel = "None (Addressable Asset)";
}
else
{
this.NoneSelectedLabel = $"None (Addressable {this.targetType.GetNiceName()})";
}
this.restrictions = new List<AssetReferenceUIRestriction>();
foreach (var attr in this.Property.Attributes)
{
if (attr is AssetReferenceUIRestriction r)
{
this.restrictions.Add(r);
}
}
this.disallowSubAssets = Property.GetAttribute<DisallowAddressableSubAssetFieldAttribute>() != null;
this.updateShowSubAssetField = true;
}
protected override void DrawPropertyLayout(GUIContent label)
{
if (this.hideAssetReferenceField == false)
{
var value = ValueEntry.SmartValue;
// Update showSubAssetField.
if (this.updateShowSubAssetField && Event.current.type == EventType.Layout)
{
if (value == null || value.AssetGUID == null || value.editorAsset == null)
{
this.showSubAssetField = false;
}
else if (string.IsNullOrEmpty(value.SubObjectName) == false)
{
this.showSubAssetField = true;
}
else if (this.disallowSubAssets)
{
this.showSubAssetField = false;
}
else
{
var path = AssetDatabase.GUIDToAssetPath(value.AssetGUID);
if (path == null)
{
this.showSubAssetField = false;
}
else
{
this.showSubAssetField = AssetDatabase.LoadAllAssetRepresentationsAtPath(path).Length > 0;
}
}
this.updateShowSubAssetField = false;
}
var rect = SirenixEditorGUI.GetFeatureRichControlRect(label, out var controlId, out var _, out var valueRect);
Rect mainRect = valueRect;
Rect subRect = default, subPickerRect = default;
if (this.showSubAssetField)
{
subRect = mainRect.Split(1, 2).AddX(1);
mainRect = mainRect.Split(0, 2).SubXMax(1);
subPickerRect = subRect.AlignRight(16);
}
var mainPickerRect = mainRect.AlignRight(16);
// Cursor
EditorGUIUtility.AddCursorRect(mainPickerRect, MouseCursor.Link);
if (showSubAssetField)
{
EditorGUIUtility.AddCursorRect(subPickerRect, MouseCursor.Link);
}
// Selector
if (GUI.Button(mainPickerRect, "", SirenixGUIStyles.None))
{
OpenMainAssetSelector(valueRect);
}
if (showSubAssetField && GUI.Button(subPickerRect, "", SirenixGUIStyles.None))
{
OpenSubAssetSelector(valueRect);
}
// Ping
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && mainRect.Contains(Event.current.mousePosition) && value != null && value.editorAsset != null)
{
EditorGUIUtility.PingObject(value.editorAsset);
}
// Drag and drop
EditorGUI.BeginChangeCheck();
var drop = DragAndDropUtilities.DropZone(rect, null, typeof(object), false, controlId);
if (EditorGUI.EndChangeCheck())
{
T dropValue;
if (!object.ReferenceEquals(drop, null)
&& ConvertUtility.TryWeakConvert(drop, targetType, out object converted)
&& converted is UnityEngine.Object obj
&& obj != null)
{
dropValue = CreateAssetReferenceFrom(obj);
}
else
{
dropValue = drop as T;
}
if (dropValue != null)
{
if (this.disallowSubAssets && string.IsNullOrEmpty(dropValue.SubObjectName) == false)
{
dropValue.SubObjectName = null;
}
this.updateShowSubAssetField = true;
this.ValueEntry.SmartValue = dropValue;
}
}
// Drawing
if (Event.current.type == EventType.Repaint)
{
GUIContent valueLabel;
if (value == null || string.IsNullOrEmpty(value.AssetGUID) || value.editorAsset == null)
{
valueLabel = GUIHelper.TempContent(NoneSelectedLabel);
}
else
{
valueLabel = GUIHelper.TempContent(value.editorAsset.name, AssetPreview.GetMiniThumbnail(value.editorAsset));
}
GUI.Label(mainRect, valueLabel, EditorStyles.objectField);
SdfIcons.DrawIcon(mainPickerRect.SetWidth(12), SdfIconType.Record2);
if (this.showSubAssetField)
{
if (string.IsNullOrEmpty(value.SubObjectName) || value.editorAsset == null)
{
valueLabel = GUIHelper.TempContent("<none>");
}
else
{
valueLabel = GUIHelper.TempContent(value.SubObjectName);
}
GUI.Label(subRect, valueLabel, EditorStyles.objectField);
SdfIcons.DrawIcon(subPickerRect.SetWidth(12), SdfIconType.Record2);
}
}
}
else
{
this.Property.Children[0].Draw(label);
}
}
private void OpenMainAssetSelector(Rect rect)
{
var selector = new AddressableSelector("Select", this.targetType, this.restrictions, typeof(T));
selector.SelectionChanged += OnMainAssetSelect;
selector.SelectionConfirmed += OnMainAssetSelect;
selector.ShowInPopup(rect);
}
private void OpenSubAssetSelector(Rect rect)
{
if (this.ValueEntry.SmartValue == null || this.ValueEntry.SmartValue.AssetGUID == null)
return;
var path = AssetDatabase.GUIDToAssetPath(this.ValueEntry.SmartValue.AssetGUID);
if (path == null)
return;
var subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
var items = new GenericSelectorItem<UnityEngine.Object>[subAssets.Length + 1];
items[0] = new GenericSelectorItem<UnityEngine.Object>("<none>", null);
for (int i = 0; i < subAssets.Length; i++)
{
items[i + 1] = new GenericSelectorItem<UnityEngine.Object>(subAssets[i].name + " : " + subAssets[i].GetType().GetNiceName(), subAssets[i]);
}
var selector = new GenericSelector<UnityEngine.Object>("Select Sub Asset", false, items);
selector.SelectionChanged += OnSubAssetSelect;
selector.SelectionConfirmed += OnSubAssetSelect;
selector.ShowInPopup(rect);
}
private void OnMainAssetSelect(IEnumerable<AddressableAssetEntry> selection)
{
var selected = selection.FirstOrDefault();
this.ValueEntry.SmartValue = CreateAssetReferenceFrom(selected);
this.updateShowSubAssetField = true;
}
private void OnSubAssetSelect(IEnumerable<UnityEngine.Object> selection)
{
if (this.ValueEntry == null || this.ValueEntry.SmartValue.AssetGUID == null)
return;
var selected = selection.FirstOrDefault();
this.ValueEntry.SmartValue.SetEditorSubObject(selected);
}
private T CreateAssetReferenceFrom(AddressableAssetEntry entry)
{
if (entry != null)
{
return CreateAssetReferenceFrom(entry.TargetAsset);
}
else
{
return null;
}
}
private T CreateAssetReferenceFrom(UnityEngine.Object obj)
{
var guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(obj));
if (guid == null) return null;
var instance = (T)Activator.CreateInstance(typeof(T), guid);
if (this.disallowSubAssets == false && AssetDatabase.IsSubAsset(obj))
{
instance.SetEditorSubObject(obj);
}
return instance;
}
public void PopulateGenericMenu(InspectorProperty property, GenericMenu genericMenu)
{
genericMenu.AddItem(new GUIContent("Set To Null"), false, () =>
{
this.ValueEntry.WeakSmartValue = null;
this.updateShowSubAssetField = true;
});
if (this.ValueEntry.SmartValue != null && string.IsNullOrEmpty(this.ValueEntry.SmartValue.SubObjectName) == false)
{
genericMenu.AddItem(new GUIContent("Remove Sub Asset"), false, () =>
{
if (this.ValueEntry.SmartValue != null)
{
this.ValueEntry.SmartValue.SetEditorSubObject(null);
}
this.updateShowSubAssetField = true;
});
}
else
{
genericMenu.AddDisabledItem(new GUIContent("Remove Sub Asset"));
}
genericMenu.AddItem(new GUIContent("Open Groups Window"), false, OdinAddressableUtility.OpenGroupsWindow);
}
}
/// <summary>
/// Draws an AssetLabelReference field.
/// </summary>
[DrawerPriority(0, 1, 0)]
public class AssetLabelReferenceDrawer : OdinValueDrawer<AssetLabelReference>, IDefinesGenericMenuItems
{
protected override bool CanDrawValueProperty(InspectorProperty property)
{
return property.GetAttribute<DrawWithUnityAttribute>() == null;
}
protected override void DrawPropertyLayout(GUIContent label)
{
var rect = SirenixEditorGUI.GetFeatureRichControlRect(label, out var controlId, out var hasKeyboardFocus, out var valueRect);
string valueLabel;
if (this.ValueEntry.SmartValue == null || string.IsNullOrEmpty(this.ValueEntry.SmartValue.labelString))
{
valueLabel = "<none>";
}
else
{
valueLabel = this.ValueEntry.SmartValue.labelString;
}
if (GUI.Button(valueRect, valueLabel, EditorStyles.popup))
{
var selector = new AddressableLabelSelector();
selector.SelectionChanged += SetLabel;
selector.SelectionConfirmed += SetLabel;
selector.ShowInPopup(valueRect);
}
}
private void SetLabel(IEnumerable<string> selection)
{
var selected = selection.FirstOrDefault();
this.ValueEntry.SmartValue = new AssetLabelReference()
{
labelString = selected,
};
}
public void PopulateGenericMenu(InspectorProperty property, GenericMenu genericMenu)
{
genericMenu.AddItem(new GUIContent("Set To Null"), false, () => property.ValueEntry.WeakSmartValue = null);
genericMenu.AddItem(new GUIContent("Open Label Window"), false, () => OdinAddressableUtility.OpenLabelsWindow());
}
}
/// <summary>
/// Odin Selector for Addressables.
/// </summary>
public class AddressableSelector : OdinSelector<AddressableAssetEntry>
{
//private static EditorPrefBool flatten = new EditorPrefBool("AddressablesSelector.Flatten", false);
private static EditorPrefEnum<SelectorListMode> listMode = new EditorPrefEnum<SelectorListMode>("AddressablesSelector.ListMode", SelectorListMode.Group);
private readonly string title;
private readonly Type filterType;
private readonly List<AssetReferenceUIRestriction> restrictions;
private readonly AssetReference assetReferenceForValidating;
public override string Title => this.title;
/// <summary>
/// Initializes a AddressableSelector.
/// </summary>
/// <param name="title">The title of the selector. Set to null for no title.</param>
/// <param name="filterType">The type of UnityEngine.Object to be selectable. For example, UnityEngine.Texture. For no restriction, set to UnityEngine.Object.</param>
/// <param name="labelRestrictions">The Addressable labels to restrict the selector to. Set to null for no label restrictions.</param>
/// <exception cref="ArgumentNullException">Throws if the filter type is null.</exception>
public AddressableSelector(string title, Type filterType, List<AssetReferenceUIRestriction> restrictions, Type assetReferenceType)
{
this.title = title;
this.filterType = filterType ?? throw new ArgumentNullException(nameof(filterType));
this.restrictions = restrictions;
if (assetReferenceType != null)
{
if (assetReferenceType.InheritsFrom<AssetReference>() == false)
{
throw new ArgumentException("Must inherit AssetReference", nameof(assetReferenceType));
}
else if (assetReferenceType.IsAbstract)
{
throw new ArgumentException("Cannot be abstract type.", nameof(assetReferenceType));
}
this.assetReferenceForValidating = (AssetReference)FormatterServices.GetUninitializedObject(assetReferenceType);
}
}
protected override void DrawToolbar()
{
bool drawTitle = !string.IsNullOrEmpty(this.Title);
bool drawSearchToolbar = this.SelectionTree.Config.DrawSearchToolbar;
bool drawButton = this.DrawConfirmSelectionButton;
if (drawTitle || drawSearchToolbar || drawButton)
{
SirenixEditorGUI.BeginHorizontalToolbar(this.SelectionTree.Config.SearchToolbarHeight);
{
DrawToolbarTitle();
DrawToolbarSearch();
EditorGUI.DrawRect(GUILayoutUtility.GetLastRect().AlignLeft(1), SirenixGUIStyles.BorderColor);
SdfIconType icon;
if (listMode.Value == SelectorListMode.Path)
icon = SdfIconType.ListNested;
else if (listMode.Value == SelectorListMode.Group)
icon = SdfIconType.ListStars;
else if (listMode.Value == SelectorListMode.Flat)
icon = SdfIconType.List;
else
icon = SdfIconType.X;
if (SirenixEditorGUI.ToolbarButton(icon, true))
{
int m = (int)listMode.Value + 1;
if (m >= (int)SelectorListMode.Max)
{
m = 0;
}
listMode.Value = (SelectorListMode)m;
this.RebuildMenuTree();
}
if (SirenixEditorGUI.ToolbarButton(SdfIconType.GearFill, true))
{
OdinAddressableUtility.OpenGroupsWindow();
}
DrawToolbarConfirmButton();
}
SirenixEditorGUI.EndHorizontalToolbar();
}
}
protected override void BuildSelectionTree(OdinMenuTree tree)
{
OdinMenuItem noneItem;
if (filterType == typeof(UnityEngine.Object))
{
noneItem = new OdinMenuItem(tree, "<none> (Addressable Asset)", null);
}
else
{
noneItem = new OdinMenuItem(tree, $"<none> (Addressable {filterType.GetNiceName()})", null);
}
noneItem.SdfIcon = SdfIconType.X;
tree.MenuItems.Add(noneItem);
tree.Config.SelectMenuItemsOnMouseDown = true;
if (AddressableAssetSettingsDefaultObject.SettingsExists)
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
foreach (var group in settings.groups)
{
if (group == null || group.name == "Built In Data") continue;
foreach (var entry in group.entries)
{
AddEntriesToTree(tree, group.name, entry);
}
}
}
foreach (var i in tree.EnumerateTree().Skip(1))
{
if (i.Value == null)
{
i.SdfIcon = SdfIconType.Folder;
}
}
}
private void AddEntriesToTree(OdinMenuTree tree, string groupName, AddressableAssetEntry entry)
{
var asset = entry.TargetAsset;
if (entry.IsFolder == false && asset.GetType().InheritsFrom(filterType) && PassesRestrictions(entry))
{
string name;
if (listMode.Value == SelectorListMode.Group)
{
name = entry.address;
}
else if (listMode.Value == SelectorListMode.Path)
{
name = System.IO.Path.GetFileNameWithoutExtension(entry.AssetPath);
}
else if (listMode.Value == SelectorListMode.Flat)
{
name = entry.address;
}
else
{
throw new Exception("Unsupported list mode: " + listMode.Value);
}
var item = new OdinMenuItem(tree, name, entry)
{
Icon = AssetPreview.GetMiniThumbnail(asset)
};
if (listMode.Value == SelectorListMode.Group)
{
OdinMenuItem groupItem = tree.GetMenuItem(groupName);
if (groupItem == null)
{
groupItem = new OdinMenuItem(tree, groupName, null);
tree.MenuItems.Add(groupItem);
}
if (entry.ParentEntry != null && entry.ParentEntry.IsFolder)
{
OdinMenuItem folderItem = null;
for (int i = 0; i < groupItem.ChildMenuItems.Count; i++)
{
if (groupItem.ChildMenuItems[i].Name == entry.ParentEntry.address)
{
folderItem = groupItem.ChildMenuItems[i];
break;
}
}
if (folderItem == null)
{
folderItem = new OdinMenuItem(tree, entry.ParentEntry.address, null);
groupItem.ChildMenuItems.Add(folderItem);
}
folderItem.ChildMenuItems.Add(item);
}
else
{
groupItem.ChildMenuItems.Add(item);
}
}
else if (listMode.Value == SelectorListMode.Path)
{
tree.AddMenuItemAtPath(System.IO.Path.GetDirectoryName(entry.AssetPath), item);
}
else if (listMode.Value == SelectorListMode.Flat)
{
tree.MenuItems.Add(item);
}
}
if (entry.IsFolder)
{
foreach (var e in entry.SubAssets)
{
AddEntriesToTree(tree, groupName, e);
}
}
}
private bool PassesRestrictions(AddressableAssetEntry entry)
{
if (restrictions == null) return true;
return OdinAddressableUtility.ValidateAssetReferenceRestrictions(restrictions, entry.MainAsset);
//for (int i = 0; i < this.restrictions.Count; i++)
//{
// if (this.restrictions[i].ValidateAsset(entry.AssetPath) == false)
// {
// return false;
// }
//}
//return true;
/* If for whatever reason Unity haven't actually implemented their restriction methods, then we can use this code to atleast implement label restriction. */
//if (this.labelRestrictions == null) return true;
//for (int i = 0; i < labelRestrictions.Length; i++)
//{
// if (entry.labels.Contains(labelRestrictions[i])) return true;
//}
//return false;
}
private enum SelectorListMode
{
Group,
Path,
Flat,
Max,
}
}
public class AddressableLabelSelector : OdinSelector<string>
{
protected override void DrawToolbar()
{
bool drawTitle = !string.IsNullOrEmpty(this.Title);
bool drawSearchToolbar = this.SelectionTree.Config.DrawSearchToolbar;
bool drawButton = this.DrawConfirmSelectionButton;
if (drawTitle || drawSearchToolbar || drawButton)
{
SirenixEditorGUI.BeginHorizontalToolbar(this.SelectionTree.Config.SearchToolbarHeight);
{
DrawToolbarTitle();
DrawToolbarSearch();
EditorGUI.DrawRect(GUILayoutUtility.GetLastRect().AlignLeft(1), SirenixGUIStyles.BorderColor);
if (SirenixEditorGUI.ToolbarButton(SdfIconType.GearFill, true))
{
OdinAddressableUtility.OpenLabelsWindow();
}
DrawToolbarConfirmButton();
}
SirenixEditorGUI.EndHorizontalToolbar();
}
}
protected override void BuildSelectionTree(OdinMenuTree tree)
{
IList<string> labels = null;
if (AddressableAssetSettingsDefaultObject.SettingsExists)
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
labels = settings.GetLabels();
}
if (labels == null) labels = Array.Empty<string>();
tree.MenuItems.Add(new OdinMenuItem(tree, "<none>", null));
for (int i = 0; i < labels.Count; i++)
{
tree.MenuItems.Add(new OdinMenuItem(tree, labels[i], labels[i]));
}
}
}
/// <summary>
/// Resolves children for AssetReference properties, and implements the <c>RegisterAssetReferenceAttributeForwardToChild</c> behaviour.
/// </summary>
/// <typeparam name="T">The concrete type of AssetReference to be drawn. For example, <c>AssetReferenceTexture</c>.</typeparam>
public class AssetReferencePropertyResolver<T> : OdinPropertyResolver<T>
where T : AssetReference
{
private static readonly Type[] attributesToForward;
static AssetReferencePropertyResolver()
{
attributesToForward = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.GetCustomAttributes<RegisterAssetReferenceAttributeForwardToChildAttribute>())
.Cast<RegisterAssetReferenceAttributeForwardToChildAttribute>()
.Select(x => x.AttributeType)
.ToArray();
}
public override int ChildNameToIndex(string name)
{
return 0;
}
public override int ChildNameToIndex(ref StringSlice name)
{
return 0;
}
public override InspectorPropertyInfo GetChildInfo(int childIndex)
{
var targetType = OdinAddressableUtility.GetAssetReferenceTargetType(typeof(T));
var getterSetterType = typeof(AssetReferenceValueGetterSetter<>).MakeGenericType(typeof(T), targetType);
var getterSetter = Activator.CreateInstance(getterSetterType) as IValueGetterSetter;
List<Attribute> attributes = new List<Attribute>
{
new ShowInInspectorAttribute(),
};
foreach (var type in attributesToForward)
{
var attr = this.Property.Attributes.FirstOrDefault(x => x.GetType() == type);
if (attr != null)
{
attributes.Add(attr);
}
}
string label = "Asset";
return InspectorPropertyInfo.CreateValue(label, 0, SerializationBackend.None, getterSetter, attributes);
}
protected override int GetChildCount(T value)
{
foreach (var attr in attributesToForward)
{
if (this.Property.Attributes.Any(x => x.GetType() == attr))
{
return 1;
}
}
return 0;
}
private class AssetReferenceValueGetterSetter<TTarget> : IValueGetterSetter<T, TTarget>
where TTarget : UnityEngine.Object
{
public bool IsReadonly => false;
public Type OwnerType => typeof(T);
public Type ValueType => typeof(TTarget);
public TTarget GetValue(ref T owner)
{
var v = owner.editorAsset;
return v as TTarget;
}
public object GetValue(object owner)
{
var v = (owner as T)?.editorAsset;
return v as TTarget;
}
public void SetValue(ref T owner, TTarget value)
{
owner.SetEditorAsset(value);
}
public void SetValue(object owner, object value)
{
(owner as T).SetEditorAsset(value as TTarget);
}
}
}
/// <summary>
/// Processes attributes for AssetReference properties.
/// </summary>
/// <typeparam name="T">The concrete type of AssetReference to be drawn. For example, <c>AssetReferenceTexture</c>.</typeparam>
public class AssetReferenceAttributeProcessor<T> : OdinAttributeProcessor<T>
where T : AssetReference
{
public override void ProcessSelfAttributes(InspectorProperty property, List<Attribute> attributes)
{
attributes.Add(new DoNotDrawAsReferenceAttribute());
attributes.Add(new HideReferenceObjectPickerAttribute());
attributes.Add(new SuppressInvalidAttributeErrorAttribute()); // TODO: Remove this with proper attribute forwarding support.
}
}
/// <summary>
/// Processes attributes for AssetLabelReference properties.
/// </summary>
public class AssetLabelReferenceAttributeProcessor : OdinAttributeProcessor<AssetLabelReference>
{
public override void ProcessSelfAttributes(InspectorProperty property, List<Attribute> attributes)
{
attributes.Add(new DoNotDrawAsReferenceAttribute());
attributes.Add(new HideReferenceObjectPickerAttribute());
}
}
/// <summary>
/// Implements conversion behaviour for addressables.
/// </summary>
[InitializeOnLoad]
internal class AssetReferenceConverter : ConvertUtility.ICustomConverter
{
private readonly Type type_AssetEntryTreeViewItem;
private WeakValueGetter<AddressableAssetEntry> get_AssetEntryTreeViewItem_entry;
static AssetReferenceConverter()
{
ConvertUtility.AddCustomConverter(new AssetReferenceConverter());
}
public AssetReferenceConverter()
{
this.type_AssetEntryTreeViewItem = TwoWaySerializationBinder.Default.BindToType("UnityEditor.AddressableAssets.GUI.AssetEntryTreeViewItem") ?? throw new Exception("Failed to find UnityEditor.AddressableAssets.GUI.AddressableAssetEntryTreeViewItem type.");
var field_AssetEntryTreeViewItem_entry = type_AssetEntryTreeViewItem.GetField("entry", Flags.AllMembers) ?? throw new Exception("Failed to find entry field in UnityEditor.AddressableAssets.GUI.AddressableAssetEntryTreeViewItem type.");
this.get_AssetEntryTreeViewItem_entry = EmitUtilities.CreateWeakInstanceFieldGetter<AddressableAssetEntry>(type_AssetEntryTreeViewItem, field_AssetEntryTreeViewItem_entry);
}
// UnityEngine.Object > AssetReference/T
// AddressableAssetEntry > AssetReference
// AssetReference/T > UnityEngine.Object
// AssetReference/T > AssetReference/T
// AddressableAssetEntry > UnityEngine.Object
public bool CanConvert(Type from, Type to)
{
var comparer = FastTypeComparer.Instance;
if (to.InheritsFrom(typeof(AssetReference)))
{
if (comparer.Equals(from, typeof(AddressableAssetEntry)) || comparer.Equals(from, type_AssetEntryTreeViewItem))
{
return true;
}
else if (from.InheritsFrom<UnityEngine.Object>())
{
if (to.InheritsFrom(typeof(AssetReferenceT<>)))
{
var baseType = to.GetGenericBaseType(typeof(AssetReferenceT<>));
var targetType = baseType.GetGenericArguments()[0];
return from.InheritsFrom(targetType);
}
else
{
return true;
}
}
else if (from.InheritsFrom(typeof(AssetReference)))
{
return to.InheritsFrom(from);
}
else
{
return false;
}
}
else if (from.InheritsFrom(typeof(AssetReference)) && to.InheritsFrom<UnityEngine.Object>())
{
return false;
}
else
{
return false;
}
}
public bool TryConvert(object obj, Type to, out object result)
{
if (obj == null)
{
result = null;
return false;
}
var comparer = FastTypeComparer.Instance;
// AssetEntryTreeViewItems is a UI element container for AddressableAssetEntry.
// With this we can just treat AssetEntryTreeViewItems as an AddressableAssetEntry.
if (comparer.Equals(obj.GetType(), type_AssetEntryTreeViewItem))
{
obj = get_AssetEntryTreeViewItem_entry(ref obj);
}
if (to.InheritsFrom(typeof(AssetReference)))
{
Type assetType;
if (to.InheritsFrom(typeof(AssetReferenceT<>)))
{
var baseType = to.GetGenericBaseType(typeof(AssetReferenceT<>));
assetType = baseType.GetGenericArguments()[0];
}
else
{
assetType = typeof(UnityEngine.Object);
}
if (obj is UnityEngine.Object uObj)
{
if (obj.GetType().InheritsFrom(assetType))
{
string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(uObj));
if (string.IsNullOrEmpty(guid) == false)
{
result = CreateReference(to, uObj);
return true;
}
else
{
result = null;
return false;
}
}
else
{
result = null;
return false;
}
}
else if (obj is AddressableAssetEntry entry)
{
if (entry.TargetAsset.GetType().InheritsFrom(assetType))
{
result = CreateReference(to, entry.TargetAsset);
return true;
}
else
{
result = null;
return false;
}
}
else if (obj is AssetReference reference)
{
if (TryGetReferencedAsset(reference, assetType, out var asset))
{
result = CreateReference(to, asset);
return true;
}
else
{
result = null;
return false;
}
}
else
{
result = null;
return false;
}
}
else if (to.InheritsFrom(typeof(UnityEngine.Object)) && obj is AssetReference reference)
{
if (TryGetReferencedAsset(reference, to, out var asset))
{
result = asset;
return true;
}
else
{
result = null;
return false;
}
}
else if (to.InheritsFrom(typeof(UnityEngine.Object)) && obj is AddressableAssetEntry entry)
{
var target = entry.TargetAsset;
if (target == null)
{
result = null;
return false;
}
else if (target.GetType().InheritsFrom(to))
{
result = target;
return true;
}
else if (ConvertUtility.TryWeakConvert(target, to, out var converted))
{
result = converted;
return true;
}
else
{
result = null;
return false;
}
}
else
{
result = null;
return false;
}
}
private bool TryGetReferencedAsset(AssetReference reference, Type to, out UnityEngine.Object asset)
{
if (reference.AssetGUID == null)
{
asset = null;
return false;
}
var path = AssetDatabase.GUIDToAssetPath(reference.AssetGUID);
if (reference.SubObjectName != null)
{
asset = null;
var subassets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
for (int i = 0; i < subassets.Length; i++)
{
if (subassets[i].name == reference.SubObjectName)
{
asset = subassets[i];
break;
}
}
}
else
{
asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path);
}
if (asset != null)
{
if (asset.GetType().InheritsFrom(to))
{
return true;
}
else if (ConvertUtility.TryWeakConvert(asset, to, out var converted))
{
asset = (UnityEngine.Object)converted;
return true;
}
else
{
asset = null;
return false;
}
}
else
{
return false;
}
}
private AssetReference CreateReference(Type type, UnityEngine.Object obj)
{
var reference = (AssetReference)Activator.CreateInstance(type, new string[] { AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(obj)) });
if (AssetDatabase.IsSubAsset(obj))
{
reference.SetEditorAsset(obj);
}
return reference;
}
}
/// <summary>
/// Odin Inspector utility methods for working with addressables.
/// </summary>
public static class OdinAddressableUtility
{
private readonly static Action openAddressableWindowAction;
static OdinAddressableUtility()
{
var type = TwoWaySerializationBinder.Default.BindToType("UnityEditor.AddressableAssets.GUI.AddressableAssetsWindow") ?? throw new Exception("");
var method = type.GetMethod("Init", Flags.AllMembers) ?? throw new Exception("");
openAddressableWindowAction = (Action)Delegate.CreateDelegate(typeof(Action), method);
}
/// <summary>
/// Opens the addressables group settings window.
/// </summary>
public static void OpenGroupsWindow()
{
openAddressableWindowAction();
}
/// <summary>
/// Opens the addressables labels settings window.
/// </summary>
public static void OpenLabelsWindow()
{
if (!AddressableAssetSettingsDefaultObject.SettingsExists) return;
EditorWindow.GetWindow<LabelWindow>().Intialize(AddressableAssetSettingsDefaultObject.Settings);
}
/// <summary>
/// Converts the specified object into an addressable.
/// </summary>
/// <param name="obj">The object to make addressable.</param>
/// <param name="group">The addressable group to add the object to.</param>
public static void MakeAddressable(UnityEngine.Object obj, AddressableAssetGroup group)
{
if (!AddressableAssetSettingsDefaultObject.SettingsExists) return;
var settings = AddressableAssetSettingsDefaultObject.Settings;
var guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(obj));
var entry = settings.CreateOrMoveEntry(guid, group, false, false);
entry.address = AssetDatabase.GUIDToAssetPath(guid);
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryCreated, entry, false, true);
}
/// <summary>
/// Gets the type targeted by an AssetReference type. For example, returns Texture for AssetReferenceTexture.
/// Returns UnityEngine.Object for AssetReference type.
/// </summary>
/// <param name="assetReferenceType">A type of AssetReference, for example, AssetReferenceTexture.</param>
/// <returns>
/// If the given type inherits AssetRefernceT&lt;T&gt;, then the method returns the generic T argument.
/// If the given type is AssetReference, then the method returns UnityEngine.Object.
/// </returns>
/// <exception cref="ArgumentNullException">Throws if given parameter is null.</exception>
/// <exception cref="ArgumentException">Throws if the given type does not inherit or is AssetReference.</exception>
public static Type GetAssetReferenceTargetType(Type assetReferenceType)
{
if (assetReferenceType == null) throw new ArgumentNullException(nameof(assetReferenceType));
if (assetReferenceType.InheritsFrom(typeof(AssetReferenceT<>)))
{
var genericBase = assetReferenceType.GetGenericBaseType(typeof(AssetReferenceT<>));
return genericBase.GetGenericArguments()[0];
}
else
{
return typeof(UnityEngine.Object);
}
}
/// <summary>
/// Validate an asset against a list of AssetReferenceUIRestrictions.
/// </summary>
/// <param name="restrictions">The restrictions to apply.</param>
/// <param name="asset">The asset to validate.</param>
/// <returns>Returns true if the asset passes all restrictions. Otherwise false.</returns>
/// <exception cref="Exception">Throws if Addressable Settings have not been created.</exception>
/// <exception cref="ArgumentNullException">Throws if restrictions or asset is null.</exception>
public static bool ValidateAssetReferenceRestrictions(List<AssetReferenceUIRestriction> restrictions, UnityEngine.Object asset)
{
return ValidateAssetReferenceRestrictions(restrictions, asset, out _);
}
/// <summary>
/// Validate an asset against a list of AssetReferenceUIRestrictions.
/// </summary>
/// <param name="restrictions">The restrictions to apply.</param>
/// <param name="asset">The asset to validate.</param>
/// <param name="failedRestriction">The first failed restriction. <c>null</c> if no restrictions failed.</param>
/// <returns>Returns true if the asset passes all restrictions. Otherwise false.</returns>
/// <exception cref="Exception">Throws if Addressable Settings have not been created.</exception>
/// <exception cref="ArgumentNullException">Throws if restrictions or asset is null.</exception>
public static bool ValidateAssetReferenceRestrictions(List<AssetReferenceUIRestriction> restrictions, UnityEngine.Object asset, out AssetReferenceUIRestriction failedRestriction)
{
if (AddressableAssetSettingsDefaultObject.SettingsExists == false) throw new Exception("Addressable Settings have not been created.");
_ = restrictions ?? throw new ArgumentNullException(nameof(restrictions));
_ = asset ?? throw new ArgumentNullException(nameof(asset));
for (int i = 0; i < restrictions.Count; i++)
{
if (restrictions[i] is AssetReferenceUILabelRestriction labels)
{
/* Unity, in all its wisdom, have apparently decided not to implement their AssetReferenceRestriction attributes in some versions(?)
* So, to compensate, we're going to manually validate the label restriction attribute, so atleast that works. */
var guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(asset));
var entry = AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid, true);
if (entry.labels.Any(x => labels.m_AllowedLabels.Contains(x)) == false)
{
failedRestriction = labels;
return false;
}
}
else if (restrictions[i].ValidateAsset(asset) == false)
{
failedRestriction = restrictions[i];
return false;
}
}
failedRestriction = null;
return true;
}
}
}
#endif