为什么要这个脚本
1,使用Addressable管理资源话,根据资源的寻址地址,加载对应的图片并且更换image的sprite。
需求
1,根据图片资源的寻址地址,加载对应的图片并且更换image的sprite(刚需),编辑器模式下同样支持(吃力不讨好)。
2,根据图片资源,完成需求1的内容。
实现
开发的时候就想着需求1的实现,当想着完成工具的时候,开发需求2的内容,需求2完成以后,回头想需求1在编辑器模式下实现没什么意义。
字段&属性
private Image _image; //图片组件属性
public Image image
{
get
{
if (_image == null)
_image = GetComponent<Image>();
return _image;
}
}
[SerializeField]
private Sprite imageSprite; //精灵资源属性
public Sprite ImageSprite
{
get
{
return imageSprite;
}
set
{
imageSprite = value;
image.sprite = value; //精灵资源修改时,更换图片的精灵
}
}
[SerializeField]
private string aai_image = null; //图片资源寻址地址属性
public string AAI_Image
{
get
{
return aai_image;
}
set
{
if (aai_image != value)
{
aai_image = value;
RefreshImage(); //值修改时,刷新图片
}
}
}
private IEnumerator coroutinue; //迭代器,用作协程
需求1
设置寻址地址时,更换图片。毫无疑问使用属性实现,但是实践的时候发现,编辑器模式下对inspector面板赋值,不会触发回调。(monobehaviour里面的字段真的很神奇,有参构造自定义类,标记为可序列化是都能直接实例化,我通过反射都做不到)
所以另辟蹊径,实现OnValidate方法,该方法会在inspector数值发生变化是回调。
public void OnValidate()
{
#if UNITY_EDITOR
RefreshSprite(); //刷新精灵
RefreshImage(); //刷新图片
#endif
}
public void RefreshImage()
{
#if UNITY_EDITOR
//AssetDatabase.GetAssetPath是Editor命名空间下的方法
//默认把资源路径作为寻址地址
//寻址地址和图片精灵寻址地址相同就不用更新
string imageAssetPath = AssetDatabase.GetAssetPath(image.sprite);
if (imageAssetPath == aai_image)
{
return;
}
#endif
//迭代器为空或者迭代器执行完,才刷新图片
if (coroutinue == null || !coroutinue.MoveNext())
{
//迭代器,图片存在并且加载完成后,替换图片的精灵,替换脚本的精灵变量值
coroutinue = AssetUtil.AA_Exist<Sprite>(aai_image, (sprite) =>
{
//编辑器模式下到运行模式下式,会销毁脚本对象,并生成新的脚本对象。
//所以协程执行时,this可能被销毁。
if (this != null)
{
image.sprite = sprite;
if (imageSprite != sprite)
{
imageSprite = sprite;
}
}
});
//运行模式下,使用常规的协程开启方法
if (Application.isPlaying)
{
StartCoroutine(coroutinue);
}
else if(Application.isEditor)
{
//编辑器模式下使用该方法开启协程,不然的话会阻塞进程导致unity编辑器卡死
#if UNITY_EDITOR
EditorCoroutineUtility.StartCoroutine(coroutinue, this);
#endif
}
}
}
方法补充
/// <summary>
/// 可寻址资源如果存在执行委托
/// </summary>
/// <param name="path">资源guiid路径</param>
/// <returns></returns>
public static IEnumerator AA_Exist<T>(string path, Action<T> action)
{
//这个函数只能在主线程上执行,所以用协程调度
var openHandle = Addressables.LoadResourceLocationsAsync(path, typeof(T));
//直到条件true时,退出等待
yield return new WaitUntil(() => openHandle.Status == AsyncOperationStatus.Succeeded);
if (openHandle.Task.IsCompleted && openHandle.Task.Result.Count > 0)
{
LoadAsset<T>(path, action);
}
}
/// <summary>
/// addressable 的加载资源完成后回调
/// </summary>
/// <typeparam name="T">加载资源的类型</typeparam>
/// <param name="path">加载资源的路径</param>
/// <param name="action">回调委托</param>
public static void LoadAsset<T>(string path, Action<T> action)
{
Addressables.LoadAssetAsync<T>(path).Completed += (open) =>
{
action(open.Result);
};
}
需求2
public void RefreshSprite()
{
if (imageSprite == image.sprite)
{
return;
}
#if UNITY_EDITOR
string assetPath = AssetDatabase.GetAssetPath(imageSprite);
AssetUtil.AA_CreateOrMove("Sprites", assetPath);
if (aai_image != assetPath)
aai_image = assetPath;
#endif
}
方法补充
/// <summary>
/// addressable 创建或可寻址资源到Group中
/// 默认assetPath为资源的寻址地址
/// </summary>
/// <param name="GroupName">资源的组名</param>
/// <param name="assetPath">资源的寻址地址</param>
public static void AA_CreateOrMove(string GroupName, string assetPath)
{
#if UNITY_EDITOR
AddressableAssetSettings addressableSettings = AddressableAssetSettingsDefaultObject.Settings;
AddressableAssetGroup customGroup = addressableSettings.FindGroup(GroupName);
if (customGroup == null)
{
customGroup = new AddressableAssetGroup();
customGroup.name = GroupName;
}
string guiId = AssetDatabase.AssetPathToGUID(assetPath);
addressableSettings.RemoveAssetEntry(guiId);
addressableSettings.CreateOrMoveEntry(guiId, customGroup);
EditorUtility.SetDirty(addressableSettings);
#endif
}
总结
在编辑器模式下支持寻址地址更换图片精灵没什么必要,因为根据图片资源更换资源太方便了,还不用资源判空操作。
对于需要在主线程上调度的异步方法(例:Addressables.LoadResourceLocationsAsync(path, typeof(T))),使用协程再合适不过了。
编辑器模式下特有的协程打开方式EditorCoroutine StartCoroutine(IEnumerator routine, object owner),(编辑器模式下到运行模式下式,会销毁脚本对象,并生成新的脚本对象,所以协程执行时,this可能被销毁,从脚本执行的表象上来看是这样的)。
unity的inspector会忽略属性
OnValidate方法再inspector面板刷新后回调
存在的问题
多做了一个吃力不讨好的功能,但是了解了一些问题的处理。
coroutinue变量名换成imageRefreshIterator会更好点。
有时候面板上赋值不会立刻刷新image组件,再次点击unity编辑器任意地方时才刷新。
项目地址
脚本文件:AsyncImage