1934 lines
72 KiB
C#
1934 lines
72 KiB
C#
//-----------------------------------------------------------------------
|
|
// <copyright file="AddressablesInspectors.cs" company="Sirenix ApS">
|
|
// Copyright (c) Sirenix ApS. All rights reserved.
|
|
// </copyright>
|
|
//-----------------------------------------------------------------------
|
|
|
|
#if !SIRENIX_INTERNAL
|
|
#pragma warning disable
|
|
#endif
|
|
|
|
using System;
|
|
using Sirenix.OdinInspector;
|
|
|
|
[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 Sirenix.OdinInspector.Modules.Addressables.Editor.Internal;
|
|
using Sirenix.Reflection.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 UnityEngine.U2D;
|
|
using UnityEditor.U2D;
|
|
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[] validMainAssetTypes;
|
|
private Type targetType;
|
|
private bool targetTypeIsNotValidMainAsset;
|
|
private string NoneSelectedLabel;
|
|
//private string[] labelRestrictions;
|
|
private bool showSubAssetField;
|
|
|
|
private bool updateShowSubAssetField;
|
|
|
|
private bool disallowSubAssets_Backing;
|
|
|
|
private bool ActuallyDisallowSubAssets => this.disallowSubAssets_Backing && !this.targetTypeIsNotValidMainAsset;
|
|
|
|
private List<AssetReferenceUIRestriction> restrictions;
|
|
|
|
private bool isSpriteAtlas;
|
|
|
|
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.EnsureNotRealNull();
|
|
|
|
this.validMainAssetTypes = OdinAddressableUtility.GetAssetReferenceValidMainAssetTypes(typeof(T));
|
|
this.targetType = OdinAddressableUtility.GetAssetReferenceTargetType(typeof(T));
|
|
this.targetTypeIsNotValidMainAsset = this.validMainAssetTypes.Contains(this.targetType) == false;
|
|
|
|
this.isSpriteAtlas = this.validMainAssetTypes.Length > 0 && this.validMainAssetTypes[0] == typeof(SpriteAtlas);
|
|
|
|
if (this.targetType == typeof(UnityEngine.Object))
|
|
{
|
|
this.NoneSelectedLabel = "None (Addressable Asset)";
|
|
}
|
|
else if (this.validMainAssetTypes.Length > 1 || this.validMainAssetTypes[0] != this.targetType)
|
|
{
|
|
this.NoneSelectedLabel = $"None (Addressable [{string.Join("/", this.validMainAssetTypes.Select(n => n.GetNiceName()))}]>{this.targetType.GetNiceName()})";
|
|
}
|
|
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_Backing = Property.GetAttribute<DisallowAddressableSubAssetFieldAttribute>() != null;
|
|
|
|
this.updateShowSubAssetField = true;
|
|
}
|
|
|
|
private string lastGuid;
|
|
|
|
protected override void DrawPropertyLayout(GUIContent label)
|
|
{
|
|
if (this.disallowSubAssets_Backing && this.targetTypeIsNotValidMainAsset)
|
|
{
|
|
SirenixEditorGUI.WarningMessageBox($"This {typeof(T).GetNiceName()} field has been marked as not allowing sub assets, but the target type '{this.targetType.GetNiceName()}' is not a valid main asset for {typeof(T).GetNiceName()}, so the target value *must* be a sub asset. Therefore sub assets have been enabled. (Valid main asset types for {typeof(T).GetNiceName()} are: {string.Join(", ", this.validMainAssetTypes.Select(t => t.GetNiceName()))})");
|
|
}
|
|
|
|
if (this.hideAssetReferenceField == false)
|
|
{
|
|
var value = ValueEntry.SmartValue;
|
|
|
|
if (this.lastGuid != this.ValueEntry.SmartValue?.AssetGUID)
|
|
{
|
|
this.updateShowSubAssetField = true;
|
|
}
|
|
|
|
this.lastGuid = this.ValueEntry.SmartValue?.AssetGUID;
|
|
|
|
// 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.ActuallyDisallowSubAssets)
|
|
{
|
|
this.showSubAssetField = false;
|
|
}
|
|
else
|
|
{
|
|
var path = AssetDatabase.GUIDToAssetPath(value.AssetGUID);
|
|
|
|
if (path == null)
|
|
{
|
|
this.showSubAssetField = false;
|
|
}
|
|
else
|
|
{
|
|
var mainAsset = AssetDatabase.LoadMainAssetAtPath(path);
|
|
this.showSubAssetField = OdinAddressableUtility.EnumerateAllActualAndVirtualSubAssets(mainAsset, path).Any();
|
|
}
|
|
}
|
|
|
|
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())
|
|
{
|
|
this.EnsureNotRealNull();
|
|
|
|
if (this.ConvertToValidAssignment(drop, out Object obj, out bool isSubAssetAssignment))
|
|
{
|
|
if (this.isSpriteAtlas && obj is Sprite sprite)
|
|
{
|
|
foreach (SpriteAtlas spriteAtlas in AssetDatabase_Internals.FindAssets<SpriteAtlas>(String.Empty, false, AssetDatabaseSearchArea.AllAssets))
|
|
{
|
|
if (!spriteAtlas.CanBindTo(sprite))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
this.SetMainAndSubAsset(spriteAtlas, sprite);
|
|
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (isSubAssetAssignment)
|
|
{
|
|
string path = AssetDatabase.GetAssetPath(obj);
|
|
|
|
UnityEngine.Object mainAsset = AssetDatabase.LoadMainAssetAtPath(path);
|
|
|
|
if (mainAsset != null)
|
|
{
|
|
if (mainAsset is Sprite mainAssetSprite)
|
|
{
|
|
this.SetMainAndSubAsset(mainAssetSprite, obj);
|
|
}
|
|
else
|
|
{
|
|
this.SetMainAndSubAsset(mainAsset, obj);
|
|
}
|
|
}
|
|
|
|
this.updateShowSubAssetField = true;
|
|
}
|
|
else
|
|
{
|
|
var isSet = false;
|
|
|
|
if (string.IsNullOrEmpty(this.ValueEntry.SmartValue.SubObjectName))
|
|
{
|
|
if (obj is Sprite)
|
|
{
|
|
Object[] subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GetAssetPath(obj));
|
|
|
|
if (subAssets.Length > 0)
|
|
{
|
|
this.SetMainAndSubAsset(obj, subAssets[0]);
|
|
|
|
isSet = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isSet)
|
|
{
|
|
this.SetMainAsset(obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.ActuallyDisallowSubAssets &&
|
|
!this.targetTypeIsNotValidMainAsset &&
|
|
!string.IsNullOrEmpty(this.ValueEntry.SmartValue.SubObjectName))
|
|
{
|
|
this.SetSubAsset(null);
|
|
}
|
|
}
|
|
else if (drop == null)
|
|
{
|
|
this.SetMainAsset(null);
|
|
}
|
|
}
|
|
|
|
// Drawing
|
|
if (Event.current.type == EventType.Repaint)
|
|
{
|
|
GUIContent valueLabel;
|
|
if (value == null || string.IsNullOrEmpty(value.AssetGUID) || value.editorAsset == null)
|
|
{
|
|
valueLabel = GUIHelper.TempContent(NoneSelectedLabel);
|
|
}
|
|
else if (showSubAssetField)
|
|
{
|
|
var path = AssetDatabase.GUIDToAssetPath(value.AssetGUID);
|
|
var assetName = System.IO.Path.GetFileNameWithoutExtension(path);
|
|
|
|
|
|
valueLabel = GUIHelper.TempContent(assetName, GetTheDamnPreview(value.editorAsset));
|
|
}
|
|
else
|
|
{
|
|
valueLabel = GUIHelper.TempContent(value.editorAsset.name, GetTheDamnPreview(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 static Texture2D GetTheDamnPreview(UnityEngine.Object obj)
|
|
{
|
|
Texture2D img = obj as Texture2D;
|
|
|
|
if (img == null)
|
|
{
|
|
img = (obj as Sprite)?.texture;
|
|
}
|
|
|
|
if (img == null)
|
|
{
|
|
img = AssetPreview.GetMiniThumbnail(obj);
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
private bool ConvertToValidAssignment(object drop, out UnityEngine.Object converted, out bool isSubAssetAssignment)
|
|
{
|
|
converted = null;
|
|
isSubAssetAssignment = false;
|
|
|
|
bool isDefinitelyMainAssetAssignment = false;
|
|
|
|
if (object.ReferenceEquals(drop, null)) return false;
|
|
|
|
if (!ConvertUtility.TryWeakConvert(drop, this.targetType, out object convertedObj))
|
|
{
|
|
for (int i = 0; i < this.validMainAssetTypes.Length; i++)
|
|
{
|
|
if (ConvertUtility.TryWeakConvert(drop, this.validMainAssetTypes[i], out convertedObj))
|
|
{
|
|
isDefinitelyMainAssetAssignment = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (convertedObj == null || !(convertedObj is UnityEngine.Object unityObj) || unityObj == null) return false;
|
|
|
|
converted = unityObj;
|
|
|
|
if (isDefinitelyMainAssetAssignment)
|
|
{
|
|
isSubAssetAssignment = false;
|
|
return true;
|
|
}
|
|
else if (AssetDatabase.IsSubAsset(converted))
|
|
{
|
|
if (this.ActuallyDisallowSubAssets)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
isSubAssetAssignment = true;
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void OpenMainAssetSelector(Rect rect)
|
|
{
|
|
this.EnsureNotRealNull();
|
|
|
|
var selector = new AddressableSelector("Select", this.validMainAssetTypes, this.restrictions, typeof(T));
|
|
|
|
bool isUnityRoot = this.Property.SerializationRoot?.ValueEntry.WeakSmartValue is UnityEngine.Object;
|
|
|
|
if (isUnityRoot)
|
|
{
|
|
Undo.IncrementCurrentGroup();
|
|
int undoIndex = Undo.GetCurrentGroup();
|
|
|
|
selector.SelectionCancelled += () => { Undo.RevertAllDownToGroup(undoIndex); };
|
|
|
|
selector.SelectionConfirmed += entries =>
|
|
{
|
|
Undo.RevertAllDownToGroup(undoIndex);
|
|
|
|
this.OnMainAssetSelect(entries.FirstOrDefault());
|
|
};
|
|
}
|
|
else
|
|
{
|
|
selector.SelectionConfirmed += entries => { this.OnMainAssetSelect(entries.FirstOrDefault()); };
|
|
}
|
|
|
|
selector.SelectionChangedWithType += (type, entries) =>
|
|
{
|
|
if (type == SelectionChangedType.SelectionCleared)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AddressableAssetEntry entry = entries.FirstOrDefault();
|
|
|
|
this.OnMainAssetSelect(entry);
|
|
};
|
|
|
|
selector.ShowInPopup(rect);
|
|
}
|
|
|
|
private void OpenSubAssetSelector(Rect rect)
|
|
{
|
|
this.EnsureNotRealNull();
|
|
|
|
if (this.ValueEntry.SmartValue == null || this.ValueEntry.SmartValue.AssetGUID == null)
|
|
return;
|
|
|
|
var path = AssetDatabase.GUIDToAssetPath(this.ValueEntry.SmartValue.AssetGUID);
|
|
if (path == null)
|
|
return;
|
|
|
|
var mainAsset = AssetDatabase.LoadMainAssetAtPath(path);
|
|
|
|
List<Object> subAssets;
|
|
|
|
if (mainAsset != null && mainAsset is SpriteAtlas)
|
|
{
|
|
subAssets = OdinAddressableUtility.EnumerateAllActualAndVirtualSubAssets(mainAsset, path)
|
|
.Where(val => val != null && (val is Sprite || val is Texture2D))
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
subAssets = OdinAddressableUtility.EnumerateAllActualAndVirtualSubAssets(mainAsset, path)
|
|
.Where(val => val != null && this.targetType.IsInstanceOfType(val))
|
|
.ToList();
|
|
}
|
|
|
|
var items = new GenericSelectorItem<UnityEngine.Object>[subAssets.Count + 1];
|
|
|
|
items[0] = new GenericSelectorItem<UnityEngine.Object>("<none>", null);
|
|
for (int i = 0; i < subAssets.Count; i++)
|
|
{
|
|
var item = new GenericSelectorItem<UnityEngine.Object>(subAssets[i].name, subAssets[i]);
|
|
items[i + 1] = item;
|
|
}
|
|
|
|
var selector = new GenericSelector<UnityEngine.Object>("Select Sub Asset", false, items);
|
|
|
|
selector.SelectionChanged += OnSubAssetSelect;
|
|
selector.SelectionConfirmed += OnSubAssetSelect;
|
|
|
|
selector.ShowInPopup(rect);
|
|
}
|
|
|
|
private void OnMainAssetSelect(AddressableAssetEntry entry) => this.UpdateAssetReference(entry);
|
|
|
|
private void OnSubAssetSelect(IEnumerable<UnityEngine.Object> selection)
|
|
{
|
|
if (this.ValueEntry == null || this.ValueEntry.SmartValue.AssetGUID == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UnityEngine.Object selected = selection.FirstOrDefault();
|
|
|
|
this.SetSubAsset(selected);
|
|
}
|
|
|
|
private void UpdateAssetReference(AddressableAssetEntry entry)
|
|
{
|
|
if (entry == null)
|
|
{
|
|
this.SetMainAsset(null);
|
|
|
|
return;
|
|
}
|
|
|
|
if (typeof(T).InheritsFrom<AssetReferenceAtlasedSprite>())
|
|
{
|
|
this.SetMainAsset(entry.MainAsset);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
if (typeof(T).InheritsFrom<AssetReferenceSprite>())
|
|
{
|
|
UnityEngine.Object subObject = null;
|
|
|
|
string path = AssetDatabase.GetAssetPath(entry.TargetAsset);
|
|
|
|
if (AssetDatabase.GetMainAssetTypeAtPath(path) == typeof(SpriteAtlas))
|
|
{
|
|
if (!(entry.TargetAsset is SpriteAtlas))
|
|
{
|
|
subObject = entry.TargetAsset;
|
|
}
|
|
}
|
|
|
|
this.SetMainAndSubAsset(entry.MainAsset, subObject);
|
|
}
|
|
else if (!this.ActuallyDisallowSubAssets && AssetDatabase.IsSubAsset(entry.TargetAsset))
|
|
{
|
|
this.SetMainAndSubAsset(entry.MainAsset, entry.TargetAsset);
|
|
}
|
|
else
|
|
{
|
|
this.SetMainAsset(entry.MainAsset);
|
|
}
|
|
}
|
|
|
|
private T CreateAssetReferenceFrom(AddressableAssetEntry entry)
|
|
{
|
|
if (entry != null)
|
|
{
|
|
return CreateAssetReferenceFrom(entry.TargetAsset);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private T CreateAssetReferenceFrom(UnityEngine.Object mainAsset, UnityEngine.Object subAsset)
|
|
{
|
|
var path = AssetDatabase.GetAssetPath(mainAsset);
|
|
var guid = AssetDatabase.AssetPathToGUID(path);
|
|
|
|
if (guid == null) return null;
|
|
|
|
var instance = (T)Activator.CreateInstance(typeof(T), guid);
|
|
instance.SetEditorSubObject(subAsset);
|
|
return instance;
|
|
}
|
|
|
|
private T CreateAssetReferenceFrom(UnityEngine.Object obj)
|
|
{
|
|
var path = AssetDatabase.GetAssetPath(obj);
|
|
var guid = AssetDatabase.AssetPathToGUID(path);
|
|
|
|
if (guid == null) return null;
|
|
|
|
var instance = (T)Activator.CreateInstance(typeof(T), guid);
|
|
|
|
if (typeof(T).InheritsFrom<AssetReferenceSprite>())
|
|
{
|
|
if (AssetDatabase.GetMainAssetTypeAtPath(path) == typeof(SpriteAtlas))
|
|
{
|
|
if (!(obj is SpriteAtlas))
|
|
{
|
|
instance.SetEditorSubObject(obj);
|
|
}
|
|
}
|
|
}
|
|
else if (typeof(T).InheritsFrom<AssetReferenceAtlasedSprite>())
|
|
{
|
|
// No need to do anything here.
|
|
// The user will need to choose a sprite
|
|
// "sub asset" from the atlas.
|
|
}
|
|
else if (this.ActuallyDisallowSubAssets == 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.EnsureNotRealNull();
|
|
|
|
this.SetMainAsset(null);
|
|
});
|
|
|
|
if (this.ValueEntry.SmartValue != null && string.IsNullOrEmpty(this.ValueEntry.SmartValue.SubObjectName) == false)
|
|
{
|
|
genericMenu.AddItem(new GUIContent("Remove Sub Asset"), false, () =>
|
|
{
|
|
this.EnsureNotRealNull();
|
|
|
|
this.SetSubAsset(null);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
genericMenu.AddDisabledItem(new GUIContent("Remove Sub Asset"));
|
|
}
|
|
|
|
genericMenu.AddItem(new GUIContent("Open Groups Window"), false, OdinAddressableUtility.OpenGroupsWindow);
|
|
}
|
|
|
|
private void SetMainAndSubAsset(UnityEngine.Object mainAsset, UnityEngine.Object subAsset, bool setDirtyIfChanged = true)
|
|
{
|
|
string subAssetName = subAsset == null ? null : subAsset.name;
|
|
|
|
bool isDifferent = this.ValueEntry.SmartValue.editorAsset != mainAsset ||
|
|
this.ValueEntry.SmartValue.SubObjectName != subAssetName;
|
|
|
|
if (!isDifferent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.Property.SerializationRoot?.ValueEntry.WeakSmartValue is UnityEngine.Object)
|
|
{
|
|
Undo.IncrementCurrentGroup();
|
|
|
|
Undo.SetCurrentGroupName("Main- and Sub Asset Changed");
|
|
|
|
int index = Undo.GetCurrentGroup();
|
|
|
|
this.SetMainAsset(mainAsset, false);
|
|
this.SetSubAsset(subAsset, false);
|
|
|
|
Undo.CollapseUndoOperations(index);
|
|
|
|
if (setDirtyIfChanged)
|
|
{
|
|
this.Property.MarkSerializationRootDirty();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.SetMainAsset(mainAsset, false);
|
|
this.SetSubAsset(subAsset, false);
|
|
}
|
|
}
|
|
|
|
private void SetMainAsset(UnityEngine.Object asset, bool setDirtyIfChanged = true)
|
|
{
|
|
if (this.ValueEntry.SmartValue.editorAsset == asset)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.Property.RecordForUndo("Main Asset Changed");
|
|
|
|
this.ValueEntry.SmartValue.SetEditorAsset(asset);
|
|
|
|
this.updateShowSubAssetField = true;
|
|
|
|
if (setDirtyIfChanged)
|
|
{
|
|
this.Property.MarkSerializationRootDirty();
|
|
}
|
|
}
|
|
|
|
private void SetSubAsset(UnityEngine.Object asset, bool setDirtyIfChanged = true)
|
|
{
|
|
#if SIRENIX_INTERNAL
|
|
if (this.ValueEntry.SmartValue.editorAsset == null)
|
|
{
|
|
Debug.LogError("[SIRENIX INTERNAL] Attempted to assign the Sub Asset on an AssetReference without the Main Asset being assigned first.");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
string assetName = asset == null ? null : asset.name;
|
|
|
|
if (this.ValueEntry.SmartValue.SubObjectName == assetName)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.Property.RecordForUndo("Sub Asset Changed");
|
|
|
|
this.ValueEntry.SmartValue.SetEditorSubObject(asset);
|
|
|
|
this.updateShowSubAssetField = true;
|
|
|
|
if (setDirtyIfChanged)
|
|
{
|
|
this.Property.MarkSerializationRootDirty();
|
|
}
|
|
}
|
|
|
|
private void EnsureNotRealNull()
|
|
{
|
|
if (this.ValueEntry.WeakSmartValue == null)
|
|
{
|
|
this.ValueEntry.SmartValue = OdinAddressableUtility.CreateAssetReferenceGuid<T>(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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);
|
|
public event Action<SelectionChangedType, IEnumerable<AddressableAssetEntry>> SelectionChangedWithType;
|
|
|
|
private static EditorPrefEnum<SelectorListMode> listMode = new EditorPrefEnum<SelectorListMode>("AddressablesSelector.ListMode", SelectorListMode.Group);
|
|
|
|
private readonly string title;
|
|
private readonly Type[] filterTypes;
|
|
private readonly List<AssetReferenceUIRestriction> restrictions;
|
|
|
|
internal bool ShowNonAddressables;
|
|
|
|
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, pass in 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, new Type[] { filterType }, restrictions, assetReferenceType)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a AddressableSelector.
|
|
/// </summary>
|
|
/// <param name="title">The title of the selector. Set to null for no title.</param>
|
|
/// <param name="filterTypes">The types of UnityEngine.Object to be selectable. For example, UnityEngine.Texture. For no restriction, pass in an array containing 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[] filterTypes, List<AssetReferenceUIRestriction> restrictions, Type assetReferenceType)
|
|
{
|
|
this.title = title;
|
|
this.filterTypes = filterTypes ?? throw new ArgumentNullException(nameof(filterTypes));
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
this.ShowNonAddressables = SirenixEditorGUI.ToolbarToggle(this.ShowNonAddressables, EditorIcons.UnityLogo);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
this.RebuildMenuTree();
|
|
}
|
|
|
|
if (SirenixEditorGUI.ToolbarButton(SdfIconType.GearFill, true))
|
|
{
|
|
OdinAddressableUtility.OpenGroupsWindow();
|
|
}
|
|
|
|
DrawToolbarConfirmButton();
|
|
}
|
|
SirenixEditorGUI.EndHorizontalToolbar();
|
|
}
|
|
}
|
|
|
|
protected override void BuildSelectionTree(OdinMenuTree tree)
|
|
{
|
|
if (this.SelectionChangedWithType != null)
|
|
{
|
|
tree.Selection.SelectionChanged += type =>
|
|
{
|
|
IEnumerable<AddressableAssetEntry> selection = this.GetCurrentSelection();
|
|
|
|
if (this.IsValidSelection(selection))
|
|
{
|
|
this.SelectionChangedWithType(type, selection);
|
|
}
|
|
};
|
|
}
|
|
|
|
tree.Config.EXPERIMENTAL_INTERNAL_SparseFixedLayouting = true;
|
|
|
|
tree.Config.SelectMenuItemsOnMouseDown = true;
|
|
|
|
if (AddressableAssetSettingsDefaultObject.SettingsExists)
|
|
{
|
|
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings;
|
|
|
|
foreach (AddressableAssetGroup group in settings.groups)
|
|
{
|
|
if (group == null || group.name == "Built In Data")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (AddressableAssetEntry entry in group.entries)
|
|
{
|
|
this.AddEntriesToTree(tree, group.name, entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (OdinMenuItem item in tree.EnumerateTree())
|
|
{
|
|
if (item.Value == null)
|
|
{
|
|
item.SdfIcon = SdfIconType.Folder;
|
|
}
|
|
}
|
|
|
|
if (this.ShowNonAddressables)
|
|
{
|
|
var searchFilter = "";
|
|
|
|
foreach (Type filterType in this.filterTypes)
|
|
{
|
|
searchFilter += $"t:{filterType.Name} ";
|
|
}
|
|
|
|
IEnumerator<HierarchyProperty> enumerator = AssetDatabase_Internals.EnumerateAllAssets(searchFilter, false, AssetDatabaseSearchArea.InAssetsOnly);
|
|
|
|
if (enumerator.MoveNext())
|
|
{
|
|
var addedGuids = new HashSet<string>();
|
|
|
|
foreach (OdinMenuItem item in tree.EnumerateTree())
|
|
{
|
|
if (item.Value != null)
|
|
{
|
|
addedGuids.Add((item.Value as AddressableAssetEntry).guid);
|
|
}
|
|
}
|
|
|
|
const string NON_ADDRESSABLES_ITEM_NAME = "Non Addressables";
|
|
|
|
var nonAddressablesItem = new OdinMenuItem(tree, NON_ADDRESSABLES_ITEM_NAME, null) {Icon = EditorIcons.UnityLogo};
|
|
|
|
tree.MenuItems.Add(nonAddressablesItem);
|
|
|
|
do
|
|
{
|
|
HierarchyProperty current = enumerator.Current;
|
|
|
|
if (addedGuids.Contains(current.guid) || !current.isMainRepresentation)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AddressableAssetEntry entry = OdinAddressableUtility.CreateFakeAddressableAssetEntry(current.guid);
|
|
|
|
if (listMode == SelectorListMode.Flat)
|
|
{
|
|
var item = new OdinMenuItem(tree, current.name, entry) {Icon = current.icon};
|
|
|
|
nonAddressablesItem.ChildMenuItems.Add(item);
|
|
}
|
|
else
|
|
{
|
|
string path = AssetDatabase.GetAssetPath(current.instanceID);
|
|
|
|
if (!current.isFolder)
|
|
{
|
|
int extensionEndingIndex = GetExtensionsEndingIndex(path);
|
|
|
|
if (extensionEndingIndex != -1)
|
|
{
|
|
path = path.Substring(0, extensionEndingIndex);
|
|
}
|
|
}
|
|
|
|
path = RemoveBaseDirectoryFromAssetPath(path);
|
|
|
|
tree.Add($"{NON_ADDRESSABLES_ITEM_NAME}/{path}", entry, current.icon);
|
|
}
|
|
} while (enumerator.MoveNext());
|
|
|
|
nonAddressablesItem.ChildMenuItems.SortMenuItemsByName();
|
|
}
|
|
}
|
|
|
|
OdinMenuItem noneItem;
|
|
|
|
if (this.filterTypes.Contains(typeof(UnityEngine.Object)))
|
|
{
|
|
noneItem = new OdinMenuItem(tree, "<none> (Addressable Asset)", null);
|
|
}
|
|
else
|
|
{
|
|
string filterTypesJoined;
|
|
|
|
if (this.filterTypes.Length == 1)
|
|
{
|
|
filterTypesJoined = this.filterTypes[0].GetNiceName();
|
|
}
|
|
else
|
|
{
|
|
filterTypesJoined = string.Join("/", this.filterTypes.Select(t => t.GetNiceName()));
|
|
}
|
|
|
|
noneItem = new OdinMenuItem(tree, $"<none> (Addressable {filterTypesJoined})", null);
|
|
}
|
|
|
|
noneItem.SdfIcon = SdfIconType.X;
|
|
tree.MenuItems.Insert(0, noneItem);
|
|
}
|
|
|
|
private static int GetExtensionsEndingIndex(string path)
|
|
{
|
|
for (var i = path.Length - 1; i >= 0; i--)
|
|
{
|
|
if (path[i] == '\\' || path[i] == '/')
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (path[i] == '.')
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
private static string RemoveBaseDirectoryFromAssetPath(string path)
|
|
{
|
|
if (path.StartsWith("Assets/"))
|
|
{
|
|
return path.Remove(0, "Assets/".Length);
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
private void AddEntriesToTree(OdinMenuTree tree, string groupName, AddressableAssetEntry entry)
|
|
{
|
|
if (entry == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool isFolder = entry.IsFolder || AssetDatabase.IsValidFolder(entry.AssetPath);
|
|
|
|
if (isFolder)
|
|
{
|
|
entry.GatherAllAssets(null, false, false, true, null);
|
|
|
|
if (entry.SubAssets != null)
|
|
{
|
|
foreach (AddressableAssetEntry e in entry.SubAssets)
|
|
{
|
|
this.AddEntriesToTree(tree, groupName, e);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UnityEngine.Object asset = entry.TargetAsset;
|
|
|
|
if (asset == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Type assetType = asset.GetType();
|
|
var inheritsFromFilterType = false;
|
|
|
|
for (var i = 0; i < this.filterTypes.Length; i++)
|
|
{
|
|
if (this.filterTypes[i].IsAssignableFrom(assetType))
|
|
{
|
|
inheritsFromFilterType = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (inheritsFromFilterType && this.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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
foreach (var subAsset in OdinAddressableUtility.EnumerateAllActualAndVirtualSubAssets(reference.editorAsset, path))
|
|
{
|
|
if (subAsset.name == reference.SubObjectName)
|
|
{
|
|
asset = subAsset;
|
|
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);
|
|
}
|
|
|
|
public static IEnumerable<UnityEngine.Object> EnumerateAllActualAndVirtualSubAssets(UnityEngine.Object mainAsset, string mainAssetPath)
|
|
{
|
|
if (mainAsset == null)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
Object[] subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(mainAssetPath);
|
|
|
|
foreach (Object subAsset in subAssets)
|
|
{
|
|
yield return subAsset;
|
|
}
|
|
|
|
// The sprites/textures in a sprite atlas are not sub assets of the atlas, but they are apparently
|
|
// still part of the atlas in a way that the addressables system considers a sub asset.
|
|
if (mainAsset is UnityEngine.U2D.SpriteAtlas atlas)
|
|
{
|
|
Object[] packables = atlas.GetPackables();
|
|
|
|
foreach (Object packable in packables)
|
|
{
|
|
if (packable == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!(packable is DefaultAsset packableFolder))
|
|
{
|
|
yield return packable;
|
|
continue;
|
|
}
|
|
|
|
string packablePath = AssetDatabase.GetAssetPath(packableFolder);
|
|
|
|
if (!AssetDatabase.IsValidFolder(packablePath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string[] files = Directory.GetFiles(packablePath, "*.*", SearchOption.AllDirectories);
|
|
|
|
foreach (string file in files)
|
|
{
|
|
if (file.EndsWith(".meta"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Type assetType = AssetDatabase.GetMainAssetTypeAtPath(file);
|
|
|
|
if (assetType != typeof(Sprite) && assetType != typeof(Texture2D))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
yield return AssetDatabase.LoadMainAssetAtPath(file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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<T>, 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);
|
|
}
|
|
}
|
|
|
|
public static Type[] GetAssetReferenceValidMainAssetTypes(Type assetReferenceType)
|
|
{
|
|
if (assetReferenceType == null) throw new ArgumentNullException(nameof(assetReferenceType));
|
|
|
|
if (assetReferenceType.InheritsFrom(typeof(AssetReferenceSprite)))
|
|
{
|
|
return new Type[]
|
|
{
|
|
typeof(Sprite),
|
|
typeof(SpriteAtlas),
|
|
typeof(Texture2D)
|
|
};
|
|
}
|
|
else if (assetReferenceType.InheritsFrom(typeof(AssetReferenceAtlasedSprite)))
|
|
{
|
|
return new Type[] { typeof(SpriteAtlas) };
|
|
}
|
|
|
|
return new Type[] { GetAssetReferenceTargetType(assetReferenceType) };
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
|
|
internal static TAssetReference CreateAssetReferenceGuid<TAssetReference>(string guid) where TAssetReference : AssetReference
|
|
{
|
|
return (TAssetReference) Activator.CreateInstance(typeof(TAssetReference), guid);
|
|
}
|
|
|
|
internal static TAssetReference CreateAssetReference<TAssetReference>(UnityEngine.Object obj) where TAssetReference : AssetReference
|
|
{
|
|
if (obj == null)
|
|
{
|
|
return CreateAssetReferenceGuid<TAssetReference>(null);
|
|
}
|
|
|
|
string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(obj));
|
|
|
|
return CreateAssetReferenceGuid<TAssetReference>(guid);
|
|
}
|
|
|
|
internal static AddressableAssetEntry CreateFakeAddressableAssetEntry(string guid)
|
|
{
|
|
var entry = (AddressableAssetEntry) FormatterServices.GetUninitializedObject(typeof(AddressableAssetEntry));
|
|
|
|
OdinAddressableReflection.AddressableAssetEntry_mGUID_Field.SetValue(entry, guid);
|
|
|
|
return entry;
|
|
}
|
|
}
|
|
}
|
|
#endif |