【Unity】UGUI模拟NGUI的UISprite-->LImage
这个组件的诞生源于上一篇:
【Unity】Addressables下的图集(SpriteAtlas)内存优化
====================================================================================
1、首先是在Image的基础上加入图集信息和切换图片的操作
在上篇里面都有写,是通过序列化图集的路径和选择Sprite的Name,初始化时通过Addressables加载图集,并完成图片选择显示
2、在编辑器模式下,通过属性面板完成图集信息和图片的选择
[CustomEditor(typeof(LImage), true)] [CanEditMultipleObjects] public class LImageInspector : ImageEditor
SpriteAtlas m_SpriteAtlas;//临时变量
string atlasName = AddressableEditorHelper.GetAssetPathByAddressableName(m_Comp.AtlasName); m_SpriteAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasName); 在属性面板的Enable方法里,获取已设置好信息的图集资源
m_SpriteAtlas = EditorGUI.ObjectField(rect, m_SpriteAtlas, typeof(SpriteAtlas), false) as SpriteAtlas; 将临时图集变量显示在属性面板,来进行替换
if (m_SpriteAtlas != null) { m_NewAtlasPath = AddressableEditorHelper.GetAddressableNameByObject(m_SpriteAtlas); if (m_AtlasPath.stringValue!= m_NewAtlasPath) { m_AtlasPath.stringValue = m_NewAtlasPath; Object[] sprites = m_SpriteAtlas.GetPackables(); Sprite newSprite = null; for (int i = 0; i < sprites.Length; i++) { if (sprites[i].name == m_Comp.SpriteName) { newSprite = AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GetAssetPath(sprites[i])); } } m_Comp.overrideSprite = newSprite; } } //判断当临时图集发生更改时,修改参与序列化的图集地址信息 //根据选择的图片名称,在新图集里面尝试寻找资源并赋值给overrideSprite //赋值给overrideSprite 是因为它不参与序列化,但又可以完成显示
if (GUILayout.Button("更换Sprite", EditorStyles.miniButton)) { if (m_SpriteAtlas != null) { SpriteSelector.Show(serializedObject, m_SpriteInfo, m_SpriteAtlas, m_Comp); } else { EditorUtility.DisplayDialog("错误", "请先设置图集!", "确定"); } } //增加更改图片的按钮,调用打开切换图片窗口的接口
3、实现根据图集展示所有资源,并提供展示选择的窗口
public Sprite[] LoadSprites(SpriteAtlas tempAtlas, string spriteName) { UnityEngine.Object[] sprites = tempAtlas.GetPackables(); Sprite[] _spriteArray = new Sprite[sprites.Length]; Texture tempTexture = null; Sprite tempSprite = null; for (int i = 0; i < sprites.Length; i++) { if ((tempTexture = sprites[i] as Texture) != null) { _spriteArray[i] = AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GetAssetPath(tempTexture)); } else if ((tempSprite = sprites[i] as Sprite) != null) { _spriteArray[i] = tempSprite; } } if (string.IsNullOrEmpty(spriteName)) { return _spriteArray; } return Array.FindAll(_spriteArray, (a) => { return a.name.ToLower().Contains(spriteName.ToLower()); }); } //更具图集获取它对应的所有Sprite,其实这里可以直接调用SpriteAtlas的GetSprites接口,我这里是之前采取源资源直接赋值给sprite属性的方案,现在clone的sprite都可以
然后用上面获取到的所有Sprite,在创建的窗口上面显示出来,并给所有图片一个点击事件
if (mImage != null) { mImage.SetSpriteEditor(sprite); EditorUtility.SetDirty(mImage.gameObject); } //在点击事件中,将选中的Sprite传递给LImage组件,进行编辑模式切换Spirte处理
public void SetSpriteEditor(Sprite _sprite) { overrideSprite = _sprite; if (_sprite != null) { m_SpriteName = _sprite.texture.name; } } //修改当前显示 //修改参与序列化的Sprite Name值
因为overrideSprite是不参与序列化的,在编辑器预览预设时,需要给LImage增加一个初始化显示的方法,和LImageInspector面板Enable时类似,更具图集地址获取本地图集资源,再根据选择SpriteName从图集资源中找到对应的资源,赋值给overrideSprite,完成预览显示
4、完结!!
解释一下为什么不采用Sprite源资源直接赋值给sprite的方法,Image的sprite字段是参与序列化的,然后结合我上一篇最后的那段话
在非运行情况下(编辑器模式下),将资源直接拖拽到Image
组件的Sprite
属性上后,该资源会被Unity序列化到场景或预制件中,即便在运行时销毁了Image
组件本身,资源本体依然保存在序列化数据中,并不会被直接卸载。运行期间,Unity对拖拽到Inspector面板上的资源进行引用计数管理,即使Image
组件被销毁,拖拽到Sprite
属性中的资源依然占用内存,因为它在场景中存在序列化引用。
所以更改overrideSprtie,减少参与序列化的资源信息,及时清理内存;
但是上面这个处理方案可能会造成显示“延迟”,因为它需要再加载,可以修改上面对图集的处理,将它更改为参与序列化,但是要注意内存!