|
一、FairyGUI简介
官方链接:https://www.fairygui.com/
1、官方描述
- 零代码:重视设计师体验,摒弃了脚本和配置文件这些需要代码思维的操作。策划和美术都可以单独制作出生产级别的UI界面。
- 高性能:运行性能处于同类产品领先水平,更为 DrawCall 优化提供了独特的策略。
- 多国语言 和分支:内置的分支和多语言机制,完全所见即所得地支持多语言版本和多渠道版本,为游戏出海提供有力支持。
2、个人看法
1、FairyGUI在产品中,已经运用两年多,在这期间也尝试过换UGUI来进行代替,但是UGUI需要实现动画、窗口切换等都需要编写代码,而FairyGUI只需要在编辑器可视化配置就行,在综合考虑之下,还是继续使用FairyGUI来作为UI方案。
2、在我们产品开发过程中,整体的界面风格需要不断进行优化,调整。基本一个月就要改动一次UI细节,制作一些复杂的UI动效,大量的交互效果(如放大、改变颜色、改变图片等等),这些我只需要在FairyGUI简单配置即可。它的便捷体现在它对UI开发的程序员非常友好。
设计师想这些都进行浮动,在FairyGUI中可视化创建动效,这个操作完全是设计师自己实现,整体编辑器布局,快捷键跟设计师常用软件相差不大。
按钮交互动效,往图片或按钮添加属性即可,还有很多在细节上对UI开发者非常友好的功能....
3、当然并不是UGUI一无是处,如果自身有一套完整的UGUI框架、屏幕自适应、UI优化方案,那完全可以继续使用UGUI,没必要去尝试FairyGUI。它更适合初创团队,因为在UI开发中费时费力,而它可以节省非常多的时间。
4、它的缺点在于开发者需要去学习一款新的编辑器、在多人同时开发UI时,需要定好UI相关协议,怎么在FairyGUI编辑器中多人开发、其他程序员要实现UI功能时需要懂一些基本的FairyGUI知识。
二、Addressables简介
该博主对Addressables进行了非常详细的描述https://www.jianshu.com/p/e79b2eef97bf
总结出一句话就是:Addressables是AssetBundle的升级版,它比AssetBundle更好用,AssetBundle资源更新中对开发者也非常友好。
如自身有一套完整的AssetBundle方案(自动打包、发布、更新下载等),没必要更换Addressables。如果没有一套属于自己的资源更新方案,那么建议使用Addressables来作为自己的资源更新方案。
三、加载Addressable中的FairyGUI资源
3.1 FairyGUI资源导出
需要去除图集中的Read/Write Enabled、Generate Mip Maps选项,目的是优化图集,减少内存消耗。
至此UI资源已经准备完毕。
3.2 标记UI资源
打开Windows-->Packages Manager
在Uniry Registry中找到Addressables Install
安装完Addressables后,我们就可以在文件夹或文件中标记为Addressable资源。
在这里我们对UI文件夹标记Addressable,如图所示:
打开Addressables窗口
默认情况下,它是位于Default Group中的,我们可以新建一个组,只放UI资源。选择UI文件夹
选择UI文件夹,我们可以创建标签,创建标签的用途是我们可以通过代码查找标签,一次把所有的UI资源都寻找到。
需要注意UI资源的路径,后面需要代码进行加载
至此,准备工作已经准备完毕,下面开始代码编写
3.3 加载UI资源
private readonly Dictionary> AssetsList = new Dictionary>();
private readonly Dictionary> KeyList = new Dictionary>();
通过Addressables.LoadResourceLocationsAsync("UI")获取到Addressables资源内该标签的所有资源
private IEnumerator Preload()
{
AsyncOperationHandle<IList<IResourceLocation>> handle = Addressables.LoadResourceLocationsAsync(&#34;UI&#34;);
yield return handle;
IList<IResourceLocation> locations = handle.Result;
//获得所有Label为UI的资源的地址
foreach (IResourceLocation location in locations)
{
string key = location.PrimaryKey.Substring(3);
key = key.Substring(0, key.IndexOf(&#39;_&#39;));
//key为FairyGUI的包名
List<string> addresses;
if (!KeyList.ContainsKey(key))
{
addresses = new List<string>();
KeyList.Add(key, addresses);
}
else
{
addresses = KeyList[key];
}
//将资源地址添加到包名对应的地址列表中
addresses.Add(location.PrimaryKey);
}
Addressables.Release(handle);
}
private IEnumerator DoAddPackages()
{
if (KeyList.Count <= 0)
{
//预载入并生成包名对应的所有资源地址的列表
yield return Preload();
}
//加载所有包
foreach (var item in KeyList)
{
//报名对应的资源列表,进行遍历载入
List<string> addresses = item.Value;
foreach (string address in addresses)
{
if (AssetsList.ContainsKey(address))
{
//目标资源已经缓存则不需要再次载入
continue;
}
AsyncOperationHandle<Object> handle = Addressables.LoadAssetAsync<Object>(address);
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
//载入后缓存
AssetsList.Add(address, handle);
}
}
UnityEngine.Debug.Log(&#34;key:&#34; + item.Key);
//执行FairyGUI的添加包函数
UIPackage.AddPackage(item.Key, LoadFunc);
}
}
LoadFunc方法可以将资源中的.bytesUI源文件,加入到UIPackage中,当执行它完毕后,从Addressables中的UI资源就算加载成功。
// 给FairyGUI的工具函数,用于提供实际的资源给FairyGUI系统
private object LoadFunc(string name, string extension, System.Type type, out DestroyMethod method)
{
method = DestroyMethod.None;
string key = $&#34;UI/{name}{extension}&#34;;
//从已载入并缓存的资源列表中查询并返回资源
return AssetsList.ContainsKey(key) ? AssetsList[key].Result : null;
}
如果对UIPackage.Addpackage中的方法很好奇,点击查看代码后
public static UIPackage AddPackage(string assetPath, LoadResource loadFunc)
{
if (_packageInstById.ContainsKey(assetPath))
return _packageInstById[assetPath];
DestroyMethod dm;
TextAsset asset = (TextAsset)loadFunc(assetPath + &#34;_fui&#34;, &#34;.bytes&#34;, typeof(TextAsset), out dm);
if (asset == null)
{
if (Application.isPlaying)
throw new Exception(&#34;FairyGUI: Cannot load ui package in &#39;&#34; + assetPath + &#34;&#39;&#34;);
else
Debug.LogWarning(&#34;FairyGUI: Cannot load ui package in &#39;&#34; + assetPath + &#34;&#39;&#34;);
}
ByteBuffer buffer = new ByteBuffer(asset.bytes);
UIPackage pkg = new UIPackage();
pkg._loadFunc = loadFunc;
pkg._assetPath = assetPath;
if (!pkg.LoadPackage(buffer, assetPath))
return null;
_packageInstById[pkg.id] = pkg;
_packageInstByName[pkg.name] = pkg;
_packageInstById[assetPath] = pkg;
_packageList.Add(pkg);
return pkg;
}
3.4 释放UI资源
资源加载与释放,将链接教程重点细读,它里面设计到优化、自适应等方案
当我们调用UIPackage.RemovePackage(&#34;package&#34;)将UI资源从FairyGUI中释放时,需要注意的是我们还需要将Addressables中的相应资源也进行释放。
还记得我们在之前加载资源时,有一个叫AssetsList字典
private readonly Dictionary<string, AsyncOperationHandle<Object>> AssetsList =
new Dictionary<string, AsyncOperationHandle<Object>>();
需要调用Addressables.Release(handle)才可以将其释放。
四、Image图片资源加载与释放
在整套UI开发中,我们还会存在部分不同需要代码进行替换;比如背包的相关图片、人物英雄图等等...
在FairyGUI中,是可以将这些图片导入到它自己的编辑器中,导出时该图片会自动形成一个图片集。理论上来说你可以打很多图集,就类似UGUI的图集,但是对于我们来说,想要将图片放入到Addressables中,用一套资源方式来管理UI资源,以及更加灵活的资源释放。
那么这就涉及到FairyGUI如何读取图片资源,以及资源释放问题了。
官方GLoader教程中,告诉我们可以自定义创建,只需要写好资源的加载方式、卸载方式即可。
public delegate void LoadCompleteCallback(NTexture texture);
public delegate void LoadErrorCallback(string error);
public class ExperimentTextureManager : MonoBehaviour
{
static ExperimentTextureManager _instance;
public static ExperimentTextureManager inst
{
get
{
if (_instance == null)
{
GameObject go = new GameObject(&#34;ExperimentTextureManager&#34;);
DontDestroyOnLoad(go);
_instance = go.AddComponent<ExperimentTextureManager>();
}
return _instance;
}
}
public const int POOL_CHECK_TIME = 30;
public const int MAX_POOL_SIZE = 0;
List<LoadItem> _items;
bool _started;
Hashtable _pool;
private Dictionary<string, AsyncOperationHandle<Texture2D>> texturePools;
void Awake()
{
_items = new List<LoadItem>();
_pool = new Hashtable();
texturePools = new Dictionary<string, AsyncOperationHandle<Texture2D>>();
//StartCoroutine(FreeIdleIcons());
}
public static void LoadIcon(string url,
LoadCompleteCallback onSuccess,
LoadErrorCallback onFail)
{
inst.loadIcon(url, onSuccess, onFail);
}
public void loadIcon(string url,
LoadCompleteCallback onSuccess,
LoadErrorCallback onFail)
{
LoadItem item = new LoadItem();
item.url = url;
item.onSuccess = onSuccess;
item.onFail = onFail;
_items.Add(item);
if (!_started)
StartCoroutine(Run());
}
public static void ReleaseIconAll()
{
inst.releaseIconAll();
}
void releaseIconAll()
{
ArrayList toRemove = null;
foreach (DictionaryEntry de in _pool)
{
string key = (string)de.Key;
NTexture texture = (NTexture)de.Value;
if (texture.refCount == 0)
{
if (toRemove == null)
toRemove = new ArrayList();
toRemove.Add(key);
texture.Dispose();
Addressables.Release(texturePools[key]);
//Debug.Log(&#34;释放资源:&#34; + key);
}
}
if (toRemove != null)
{
foreach (string key in toRemove)
{
//Debug.Log(&#34;Remove资源:&#34; + key);
_pool.Remove(key);
texturePools.Remove(key);
}
}
Resources.UnloadUnusedAssets();
Caching.ClearCache();
}
IEnumerator Run()
{
_started = true;
LoadItem item = null;
while (true)
{
if (_items.Count > 0)
{
item = _items[0];
_items.RemoveAt(0);
}
else
break;
if (_pool.ContainsKey(item.url))
{
NTexture texture = (NTexture)_pool[item.url];
texture.refCount++;
if (item.onSuccess != null)
item.onSuccess(texture);
continue;
}
string url = item.url;
if (texturePools.ContainsKey(url))
{
NTexture texture = new NTexture(texturePools[url].Result);
texture.destroyMethod = DestroyMethod.Unload;
texture.refCount++;
_pool[item.url] = texture;
if (item.onSuccess != null)
item.onSuccess(texture);
continue;
}
AsyncOperationHandle<Texture2D> handle = Addressables.LoadAssetAsync<Texture2D>(url);
yield return handle;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
NTexture texture = new NTexture(handle.Result);
texture.destroyMethod = DestroyMethod.Unload;
texture.refCount++;
_pool[item.url] = texture;
if (item.onSuccess != null)
item.onSuccess(texture);
texturePools.Add(url, handle);
}
else
{
if (item.onFail != null)
item.onFail(handle.OperationException.Message);
}
}
_started = false;
}
IEnumerator FreeIdleIcons()
{
while (true)
{
yield return new WaitForSeconds(POOL_CHECK_TIME); //check the pool every 30 seconds
int cnt = _pool.Count;
if (cnt > MAX_POOL_SIZE)
{
ArrayList toRemove = null;
foreach (DictionaryEntry de in _pool)
{
string key = (string)de.Key;
NTexture texture = (NTexture)de.Value;
if (texture.refCount == 0)
{
if (toRemove == null)
toRemove = new ArrayList();
toRemove.Add(key);
texture.Unload();
texture.Dispose();
//Addressables.ResourceManager.Release(texturePools[key]);
Addressables.Release(texturePools[key]);
Debug.Log(&#34;释放资源:&#34; + key);
cnt--;
if (cnt <= 0)
break;
}
}
if (toRemove != null)
{
foreach (string key in toRemove)
{
Debug.Log(&#34;Remove资源:&#34; + key);
_pool.Remove(key);
texturePools.Remove(key);
}
}
Resources.UnloadUnusedAssets();
Caching.ClearCache();
}
}
}
}
public class LoadItem
{
public string url;
public LoadCompleteCallback onSuccess;
public LoadErrorCallback onFail;
}
public class ExperimentGLoader : GLoader
{
protected override void LoadExternal()
{
ExperimentTextureManager.LoadIcon(this.url, OnLoadSuccess, OnLoadFail);
}
protected override void FreeExternal(NTexture texture)
{
texture.refCount--;
}
void OnLoadSuccess(NTexture texture)
{
if (string.IsNullOrEmpty(this.url))
return;
this.onExternalLoadSuccess(texture);
}
void OnLoadFail(string error)
{
Debug.Log(&#34;load &#34; + this.url + &#34; failed: &#34; + error);
this.onExternalLoadFailed();
}
}
只需要重点关注ExperimentTextureManager中的Run()和releaseIconAll(),Run方法里面执行的是从Addressable资源中进行读取,根据路径加载资源
需要在Awake()中将自定义的Loader脚本设置到FairyGUI中
UIObjectFactory.SetLoaderExtension(typeof(ExperimentGLoader));
那么怎么将图片加入到GLoader中呢?如下所示:
string url = $&#34;Experiment/Image/{veesData.number}.jpg&#34;;
if (!string.IsNullOrEmpty(url))
com.GetChild(&#34;n8&#34;).asCom.GetChild(&#34;n0&#34;).asLoader.url = url;
url:是Addressables中该图片地址
它是怎么进行工作的?
当我们设置GLoader中的url时,内部调用GLoader方法,进而ExperimentGLoader中
public class ExperimentGLoader : GLoader
{
protected override void LoadExternal()
{
ExperimentTextureManager.LoadIcon(this.url, OnLoadSuccess, OnLoadFail);
}
}
继续深入走... 协程调用Run()方法,在前面介绍中提到Run()方法主要是从Addressables进行读取,这里不对Run方法进行详解,具体代码可复制创建自定义GLoader这节代码,进行详读。
public static void LoadIcon(string url,
LoadCompleteCallback onSuccess,
LoadErrorCallback onFail)
{
inst.loadIcon(url, onSuccess, onFail);
}
public void loadIcon(string url,
LoadCompleteCallback onSuccess,
LoadErrorCallback onFail)
{
LoadItem item = new LoadItem();
item.url = url;
item.onSuccess = onSuccess;
item.onFail = onFail;
_items.Add(item);
if (!_started)
StartCoroutine(Run());
}
既然知道资源怎么加载后,那我们就可以知道资源是如何释放的;
参考ExperimentTextureManager类中的releaseIconAll()方法即可。需要在FairyGUI中将资源释放、同时也要在Addressables中将资源进行释放。
public static void ReleaseIconAll()
{
inst.releaseIconAll();
}
void releaseIconAll()
{
ArrayList toRemove = null;
foreach (DictionaryEntry de in _pool)
{
string key = (string)de.Key;
NTexture texture = (NTexture)de.Value;
if (texture.refCount == 0)
{
if (toRemove == null)
toRemove = new ArrayList();
toRemove.Add(key);
texture.Dispose();
Addressables.Release(texturePools[key]);
//Debug.Log(&#34;释放资源:&#34; + key);
}
}
if (toRemove != null)
{
foreach (string key in toRemove)
{
//Debug.Log(&#34;Remove资源:&#34; + key);
_pool.Remove(key);
texturePools.Remove(key);
}
}
Resources.UnloadUnusedAssets();
Caching.ClearCache();
}
在上述代码介绍中,我们为了防止同一个资源加载多次,一般我们会用一个资源池(Pools)
private Dictionary<string, AsyncOperationHandle<Texture2D>> texturePools;
避免多次从Addressables进行加载,节省内存。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|