using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEditor; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; using UnityEngine.Serialization; using UnityEngine.U2D; namespace UnityEngine.AddressableAssets { /// /// Generic version of AssetReference class. This should not be used directly as CustomPropertyDrawers do not support generic types. Instead use the concrete derived classes such as AssetReferenceGameObject. /// /// [Serializable] public class AssetReferenceT : AssetReference where TObject : Object { /// /// Construct a new AssetReference object. /// /// The guid of the asset. public AssetReferenceT(string guid) : base(guid) { #if UNITY_EDITOR m_DerivedClassType = typeof(TObject); #endif } /// /// Load the referenced asset as type TObject. /// This cannot be used a second time until the first load is released. If you wish to call load multiple times /// on an AssetReference, use and pass your AssetReference in as the key. /// /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// The load operation. //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadAssetAsync(*)", true)] [Obsolete] public AsyncOperationHandle LoadAsset() { return LoadAssetAsync(); } /// /// Load the referenced asset as type TObject. /// This cannot be used a second time until the first load is released. If you wish to call load multiple times /// on an AssetReference, use and pass your AssetReference in as the key. /// on an AssetReference, use Addressables.LoadAssetAsync<>() and pass your AssetReference in as the key. /// /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// The load operation. public virtual AsyncOperationHandle LoadAssetAsync() { return LoadAssetAsync(); } /// public override bool ValidateAsset(Object obj) { var type = obj.GetType(); return typeof(TObject).IsAssignableFrom(type); } /// /// Validates that the asset located at a path is allowable for this asset reference. An asset is allowable if /// it is of the correct type or if one of its sub-asset is. /// /// The path to the asset in question. /// Whether the referenced asset is valid. public override bool ValidateAsset(string mainAssetPath) { #if UNITY_EDITOR if (typeof(TObject).IsAssignableFrom(AssetDatabase.GetMainAssetTypeAtPath(mainAssetPath))) return true; var repr = AssetDatabase.LoadAllAssetRepresentationsAtPath(mainAssetPath); return repr != null && repr.Any(o => o is TObject); #else return false; #endif } #if UNITY_EDITOR internal TObject FetchAsset() { var assetPath = AssetDatabase.GUIDToAssetPath(AssetGUID); var asset = AssetDatabase.LoadAssetAtPath(assetPath, typeof(TObject)); return (TObject) asset; } #endif #if UNITY_EDITOR /// /// Type-specific override of parent editorAsset. Used by the editor to represent the main asset referenced. /// /// Editor Asset as type TObject, else null public new TObject editorAsset { get { if (CachedAsset as TObject != null || string.IsNullOrEmpty(AssetGUID)) return CachedAsset as TObject; TObject asset = FetchAsset(); if (asset == null) Debug.LogWarning("Assigned editorAsset does not match type " + typeof(TObject) + ". EditorAsset will be null."); return asset; } } #endif } /// /// GameObject only asset reference. /// [Serializable] public class AssetReferenceGameObject : AssetReferenceT { /// /// Constructs a new reference to a GameObject. /// /// The object guid. public AssetReferenceGameObject(string guid) : base(guid) {} } /// /// Texture only asset reference. /// [Serializable] public class AssetReferenceTexture : AssetReferenceT { /// /// Constructs a new reference to a Texture. /// /// The object guid. public AssetReferenceTexture(string guid) : base(guid) {} } /// /// Texture2D only asset reference. /// [Serializable] public class AssetReferenceTexture2D : AssetReferenceT { /// /// Constructs a new reference to a Texture2D. /// /// The object guid. public AssetReferenceTexture2D(string guid) : base(guid) {} } /// /// Texture3D only asset reference /// [Serializable] public class AssetReferenceTexture3D : AssetReferenceT { /// /// Constructs a new reference to a Texture3D. /// /// The object guid. public AssetReferenceTexture3D(string guid) : base(guid) {} } /// /// Sprite only asset reference. /// [Serializable] public class AssetReferenceSprite : AssetReferenceT { /// /// Constructs a new reference to a AssetReferenceSprite. /// /// The object guid. public AssetReferenceSprite(string guid) : base(guid) {} /// public override bool ValidateAsset(string path) { #if UNITY_EDITOR if (AssetDatabase.GetMainAssetTypeAtPath(path) == typeof(SpriteAtlas)) return true; var type = AssetDatabase.GetMainAssetTypeAtPath(path); bool isTexture = typeof(Texture2D).IsAssignableFrom(type); if (isTexture) { var importer = AssetImporter.GetAtPath(path) as TextureImporter; return (importer != null) && (importer.spriteImportMode != SpriteImportMode.None); } #endif return false; } #if UNITY_EDITOR /// /// Typeless override of parent editorAsset. Used by the editor to represent the main asset referenced. /// public new Object editorAsset { get { if (CachedAsset != null || string.IsNullOrEmpty(AssetGUID)) return CachedAsset; var prop = typeof(AssetReference).GetProperty("editorAsset"); return prop.GetValue(this, null) as Object; } } #endif } /// /// Assetreference that only allows atlassed sprites. /// [Serializable] public class AssetReferenceAtlasedSprite : AssetReferenceT { /// /// Constructs a new reference to a AssetReferenceAtlasedSprite. /// /// The object guid. public AssetReferenceAtlasedSprite(string guid) : base(guid) {} /// public override bool ValidateAsset(Object obj) { return obj is SpriteAtlas; } /// public override bool ValidateAsset(string path) { #if UNITY_EDITOR return AssetDatabase.GetMainAssetTypeAtPath(path) == typeof(SpriteAtlas); #else return false; #endif } #if UNITY_EDITOR /// /// SpriteAtlas Type-specific override of parent editorAsset. Used by the editor to represent the main asset referenced. /// public new SpriteAtlas editorAsset { get { if (CachedAsset != null || string.IsNullOrEmpty(AssetGUID)) return CachedAsset as SpriteAtlas; var assetPath = AssetDatabase.GUIDToAssetPath(AssetGUID); var main = AssetDatabase.LoadMainAssetAtPath(assetPath) as SpriteAtlas; if (main != null) CachedAsset = main; return main; } } #endif } /// /// Reference to an addressable asset. This can be used in script to provide fields that can be easily set in the editor and loaded dynamically at runtime. /// To determine if the reference is set, use RuntimeKeyIsValid(). /// [Serializable] public class AssetReference : IKeyEvaluator { [FormerlySerializedAs("m_assetGUID")] [SerializeField] string m_AssetGUID = ""; [SerializeField] string m_SubObjectName; [SerializeField] string m_SubObjectType = null; AsyncOperationHandle m_Operation; /// /// The AsyncOperationHandle currently being used by the AssetReference. /// For example, if you call AssetReference.LoadAssetAsync, this property will return a handle to that operation. /// public AsyncOperationHandle OperationHandle { get { return m_Operation; } internal set { m_Operation = value; #if UNITY_EDITOR if (m_Operation.Status != AsyncOperationStatus.Failed) m_ActiveAssetReferences.Add(this); #endif } } /// /// The actual key used to request the asset at runtime. RuntimeKeyIsValid() can be used to determine if this reference was set. /// public virtual object RuntimeKey { get { if (m_AssetGUID == null) m_AssetGUID = string.Empty; if (!string.IsNullOrEmpty(m_SubObjectName)) return string.Format("{0}[{1}]", m_AssetGUID, m_SubObjectName); return m_AssetGUID; } } /// /// Stores the guid of the asset. /// public virtual string AssetGUID { get { return m_AssetGUID; } } /// /// Stores the name of the sub object. /// public virtual string SubObjectName { get { return m_SubObjectName; } set { m_SubObjectName = value; } } internal virtual Type SubOjbectType { get { if (!string.IsNullOrEmpty(m_SubObjectName) && m_SubObjectType != null) return Type.GetType(m_SubObjectType); return null; } } /// /// Returns the state of the internal operation. /// /// True if the operation is valid. public bool IsValid() { return m_Operation.IsValid(); } /// /// Get the loading status of the internal operation. /// public bool IsDone { get { return m_Operation.IsDone; } } #if UNITY_EDITOR [InitializeOnLoadMethod] static void RegisterForPlaymodeChange() { EditorApplication.playModeStateChanged -= EditorApplicationOnplayModeStateChanged; EditorApplication.playModeStateChanged += EditorApplicationOnplayModeStateChanged; } static HashSet m_ActiveAssetReferences = new HashSet(); static void EditorApplicationOnplayModeStateChanged(PlayModeStateChange state) { if (EditorSettings.enterPlayModeOptionsEnabled && Addressables.reinitializeAddressables) { foreach (AssetReference reference in m_ActiveAssetReferences) { reference.ReleaseHandleWhenPlaymodeStateChanged(state); } } } void ReleaseHandleWhenPlaymodeStateChanged(PlayModeStateChange state) { if (m_Operation.IsValid()) m_Operation.Release(); } #endif /// /// Construct a new AssetReference object. /// public AssetReference() { } #if UNITY_EDITOR ~AssetReference() { m_ActiveAssetReferences.Remove(this); } #endif /// /// Construct a new AssetReference object. /// /// The guid of the asset. public AssetReference(string guid) { m_AssetGUID = guid; } //Special constructor only used when constructing in a derived class internal AssetReference(string guid, Type type) { m_AssetGUID = guid; #if UNITY_EDITOR m_DerivedClassType = type; #endif } /// /// The loaded asset. This value is only set after the AsyncOperationHandle returned from LoadAssetAsync completes. /// It will not be set if only InstantiateAsync is called. It will be set to null if release is called. /// public virtual Object Asset { get { if (!m_Operation.IsValid()) return null; return m_Operation.Result as Object; } } #if UNITY_EDITOR Object m_CachedAsset; string m_CachedGUID = ""; /// /// Cached Editor Asset. /// protected Object CachedAsset { get { if (m_CachedGUID != m_AssetGUID) { m_CachedAsset = null; m_CachedGUID = ""; } return m_CachedAsset; } set { m_CachedAsset = value; m_CachedGUID = m_AssetGUID; } } #endif /// /// String representation of asset reference. /// /// The asset guid as a string. public override string ToString() { #if UNITY_EDITOR return "[" + m_AssetGUID + "]" + CachedAsset; #else return "[" + m_AssetGUID + "]"; #endif } static AsyncOperationHandle CreateFailedOperation() { //this needs to be set in order for ResourceManager.ExceptionHandler to get hooked up to AddressablesImpl.LogException. Addressables.InitializeAsync(); return Addressables.ResourceManager.CreateCompletedOperation(default(T), new Exception("Attempting to load an asset reference that has no asset assigned to it.").Message); } /// /// Load the referenced asset as type TObject. /// This cannot be used a second time until the first load is released. If you wish to call load multiple times /// on an AssetReference, use and pass your AssetReference in as the key. /// /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// The object type. /// The load operation. //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadAssetAsync(*)", true)] [Obsolete] public AsyncOperationHandle LoadAsset() { return LoadAssetAsync(); } /// /// Loads the reference as a scene. /// This cannot be used a second time until the first load is released. If you wish to call load multiple times /// on an AssetReference, use Addressables.LoadSceneAsync() and pass your AssetReference in as the key. /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// The operation handle for the scene load. //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadSceneAsync(*)", true)] [Obsolete] public AsyncOperationHandle LoadScene() { return LoadSceneAsync(); } /// /// InstantiateAsync the referenced asset as type TObject. /// This cannot be used a second time until the first load is released. If you wish to call load multiple times /// on an AssetReference, use Addressables.InstantiateAsync() and pass your AssetReference in as the key. /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// Position of the instantiated object. /// Rotation of the instantiated object. /// The parent of the instantiated object. /// Returns the instantiation operation. //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> InstantiateAsync(*)", true)] [Obsolete] public AsyncOperationHandle Instantiate(Vector3 position, Quaternion rotation, Transform parent = null) { return InstantiateAsync(position, rotation, parent); } /// /// InstantiateAsync the referenced asset as type TObject. /// This cannot be used a second time until the first load is released. If you wish to call load multiple times /// on an AssetReference, use Addressables.InstantiateAsync() and pass your AssetReference in as the key. /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// The parent of the instantiated object. /// Option to retain world space when instantiated with a parent. /// Returns the instantiation operation. //[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> InstantiateAsync(*)", true)] [Obsolete] public AsyncOperationHandle Instantiate(Transform parent = null, bool instantiateInWorldSpace = false) { return InstantiateAsync(parent, instantiateInWorldSpace); } /// /// Load the referenced asset as type TObject. /// This cannot be used a second time until the first load is released. If you wish to call load multiple times /// on an AssetReference, use and pass your AssetReference in as the key. /// /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// The object type. /// The load operation if there is not a valid cached operation, otherwise return default operation. public virtual AsyncOperationHandle LoadAssetAsync() { AsyncOperationHandle result = default(AsyncOperationHandle); if (m_Operation.IsValid()) Debug.LogError("Attempting to load AssetReference that has already been loaded. Handle is exposed through getter OperationHandle"); else { result = Addressables.LoadAssetAsync(RuntimeKey); OperationHandle = result; } return result; } /// /// Loads the reference as a scene. /// This cannot be used a second time until the first load is unloaded. If you wish to call load multiple times /// on an AssetReference, use Addressables.LoadSceneAsync() and pass your AssetReference in as the key. /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// Scene load mode. /// If false, the scene will load but not activate (for background loading). The SceneInstance returned has an Activate() method that can be called to do this at a later point. /// Async operation priority for scene loading. /// The operation handle for the request if there is not a valid cached operation, otherwise return default operation public virtual AsyncOperationHandle LoadSceneAsync(LoadSceneMode loadMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100) { AsyncOperationHandle result = default(AsyncOperationHandle); if (m_Operation.IsValid()) Debug.LogError("Attempting to load AssetReference Scene that has already been loaded. Handle is exposed through getter OperationHandle"); else { result = Addressables.LoadSceneAsync(RuntimeKey, loadMode, activateOnLoad, priority); OperationHandle = result; } return result; } /// /// Unloads the reference as a scene. /// /// The operation handle for the scene load. public virtual AsyncOperationHandle UnLoadScene() { return Addressables.UnloadSceneAsync(m_Operation, true); } /// /// InstantiateAsync the referenced asset as type TObject. /// This cannot be used a second time until the first load is released. If you wish to call load multiple times /// on an AssetReference, use Addressables.InstantiateAsync() and pass your AssetReference in as the key. /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// Position of the instantiated object. /// Rotation of the instantiated object. /// The parent of the instantiated object. /// public virtual AsyncOperationHandle InstantiateAsync(Vector3 position, Quaternion rotation, Transform parent = null) { return Addressables.InstantiateAsync(RuntimeKey, position, rotation, parent, true); } /// /// InstantiateAsync the referenced asset as type TObject. /// This cannot be used a second time until the first load is released. If you wish to call load multiple times /// on an AssetReference, use Addressables.InstantiateAsync() and pass your AssetReference in as the key. /// See the [Loading Addressable Assets](xref:addressables-api-load-asset-async) documentation for more details. /// /// The parent of the instantiated object. /// Option to retain world space when instantiated with a parent. /// public virtual AsyncOperationHandle InstantiateAsync(Transform parent = null, bool instantiateInWorldSpace = false) { return Addressables.InstantiateAsync(RuntimeKey, parent, instantiateInWorldSpace, true); } /// public virtual bool RuntimeKeyIsValid() { Guid result; string guid = RuntimeKey.ToString(); int subObjectIndex = guid.IndexOf("["); if (subObjectIndex != -1) //This means we're dealing with a sub-object and need to convert the runtime key. guid = guid.Substring(0, subObjectIndex); return Guid.TryParse(guid, out result); } /// /// Release the internal operation handle. /// public virtual void ReleaseAsset() { if (!m_Operation.IsValid()) { Debug.LogWarning("Cannot release a null or unloaded asset."); return; } Addressables.Release(m_Operation); m_Operation = default(AsyncOperationHandle); } /// /// Release an instantiated object. /// /// The object to release. public virtual void ReleaseInstance(GameObject obj) { Addressables.ReleaseInstance(obj); } /// /// Validates that the referenced asset allowable for this asset reference. /// /// The Object to validate. /// Whether the referenced asset is valid. public virtual bool ValidateAsset(Object obj) { return true; } /// /// Validates that the referenced asset allowable for this asset reference. /// /// The path to the asset in question. /// Whether the referenced asset is valid. public virtual bool ValidateAsset(string path) { return true; } #if UNITY_EDITOR [SerializeField] #pragma warning disable CS0414 bool m_EditorAssetChanged; protected internal Type m_DerivedClassType; #pragma warning restore CS0414 /// /// Used by the editor to represent the main asset referenced. /// public virtual Object editorAsset { get { if (CachedAsset != null || string.IsNullOrEmpty(m_AssetGUID)) return CachedAsset; var asset = FetchEditorAsset(); if (m_DerivedClassType == null) return CachedAsset = asset; if (asset == null) Debug.LogWarning("Assigned editorAsset does not match type " + m_DerivedClassType + ". EditorAsset will be null."); return CachedAsset = asset; } } internal Object FetchEditorAsset() { var assetPath = AssetDatabase.GUIDToAssetPath(m_AssetGUID); var asset = AssetDatabase.LoadAssetAtPath(assetPath, m_DerivedClassType ?? AssetDatabase.GetMainAssetTypeAtPath(assetPath)); return asset; } /// /// Sets the main asset on the AssetReference. Only valid in the editor, this sets both the editorAsset attribute, /// and the internal asset GUID, which drives the RuntimeKey attribute. If the reference uses a sub object, /// then it will load the editor asset during edit mode and load the sub object during runtime. For example, if /// the AssetReference is set to a sprite within a sprite atlas, the editorAsset is the atlas (loaded during edit mode) /// and the sub object is the sprite (loaded during runtime). If called by AssetReferenceT, will set the editorAsset /// to the requested object if the object is of type T, and null otherwise. /// Object to reference /// public virtual bool SetEditorAsset(Object value) { if (value == null) { CachedAsset = null; m_AssetGUID = string.Empty; m_SubObjectName = null; m_EditorAssetChanged = true; return true; } if (CachedAsset != value) { m_SubObjectName = null; var path = AssetDatabase.GetAssetOrScenePath(value); if (string.IsNullOrEmpty(path)) { Addressables.LogWarningFormat("Invalid object for AssetReference {0}.", value); return false; } if (!ValidateAsset(path)) { Addressables.LogWarningFormat("Invalid asset for AssetReference path = '{0}'.", path); return false; } else { m_AssetGUID = AssetDatabase.AssetPathToGUID(path); Object mainAsset; if (m_DerivedClassType != null) mainAsset = LocateEditorAssetForTypedAssetReference(value, path); else { mainAsset = AssetDatabase.LoadMainAssetAtPath(path); if (value != mainAsset) SetEditorSubObject(value); } CachedAsset = mainAsset; } } m_EditorAssetChanged = true; return true; } internal Object LocateEditorAssetForTypedAssetReference(Object value, string path) { Object mainAsset; if (value.GetType() != m_DerivedClassType) { mainAsset = null; } else { mainAsset = AssetDatabase.LoadAssetAtPath(path, m_DerivedClassType); if (mainAsset != value) { mainAsset = null; var subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path); foreach (var asset in subAssets) { if (asset.GetType() == m_DerivedClassType && value == asset) { mainAsset = asset; break; } } } } if (mainAsset == null) Debug.LogWarning( "Assigned editorAsset does not match type " + m_DerivedClassType + ". EditorAsset will be null."); return mainAsset; } /// /// Sets the sub object for this asset reference. /// /// The sub object. /// True if set correctly. public virtual bool SetEditorSubObject(Object value) { if (value == null) { m_SubObjectName = null; m_SubObjectType = null; m_EditorAssetChanged = true; return true; } if (editorAsset == null) return false; if (editorAsset.GetType() == typeof(SpriteAtlas)) { var spriteName = value.name; if (spriteName.EndsWith("(Clone)")) spriteName = spriteName.Replace("(Clone)", ""); if ((editorAsset as SpriteAtlas).GetSprite(spriteName) == null) { Debug.LogWarningFormat("Unable to find sprite {0} in atlas {1}.", spriteName, editorAsset.name); return false; } m_SubObjectName = spriteName; m_SubObjectType = typeof(Sprite).AssemblyQualifiedName; m_EditorAssetChanged = true; return true; } var subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GUIDToAssetPath(m_AssetGUID)); foreach (var s in subAssets) { if (s.name == value.name && s.GetType() == value.GetType()) { m_SubObjectName = s.name; m_SubObjectType = s.GetType().AssemblyQualifiedName; m_EditorAssetChanged = true; return true; } } return false; } #endif } }