2025-07-08 10:46:31 +00:00
//-----------------------------------------------------------------------
// <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