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
}
}