fgui的ui管理框架_ET框架&&FGUI&&xasset的梦幻联动

本文介绍了如何将轻量高效的资源管理框架xasset与ET框架结合,以及适配FGUI的步骤。通过xasset的打包工具、本地资源服务器和运行时类库,实现资源管理的流畅接入。同时,提供了详细的接入代码示例和热更新流程,帮助开发者更好地理解和应用这些技术。
摘要由CSDN通过智能技术生成

前言

技能系统暂时告一段落,现在要花点时间规范一下客户端这边的资源管理以及一些流程优化,这里选择轻量高效资源管理框架xasset:xasset开源地址​github.com

版本为:https://github.com/xasset/xasset/commit/3d4983cd24ff92a63156c8078caf34b20d2d4c02​github.com

代码量很少,一天就能看个差不多,但是质量很高,如果只追求使用的话,是可以开箱即用的。

另外我对xasset底层源码做了一些修改,主要是为了迎合我们ET传统艺能await/async样式代码,所以推荐大家直接使用我项目(下文的Moba项目)中的xasset源码

想要入门此资源管理框架的可以查看:xasset入门指南 - TA养成记​www.lfzxb.top

以及视频教程:xasset4.0入门指南_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.com

为了方便大家参考,可以去我对Moba项目:烟雨迷离半世殇/NKGMobaBasedOnET​gitee.com

,来查看更加具体的接入代码,目前全部功能测试通过(资源加载,普通热更新,VFS热更新,FGUI适配)

流程预演

为了流畅接入一个框架,可以先把大体流程先构思一下

xasset使用主要分为3块打包工具配置(直接拿过来改几个路径即可使用(因为要打包到我们资源服务器指定的目录下面))

本地资源服务器(使用ET自带的Web资源服务器即可)

运行时类库(非常简单的接口使用,完全不需要关心资源管理)

打包工具

xasset打包流程分ApplyRule(配置打包规则),BuildRule(自动分析依赖关系,优化资源冗余,解决资源冲突),BuildBundle(出包)三步走,具体内容可参照上文链接

本地资源服务器

这一块是ET的内容,事实上我们只需要修改代码,把资源打到文件资源服务器指定的目录就行了

运行时类库

xasset运行时接入相对于前面两块内容较为复杂,主要包括资源热更新模块接入,API封装,FGUI资源加载适配

正式开始

xasset导入

首先导入xasset到ET,主要有Editor和Runtime这两部分内容

首先是Editor部分,把Assets/XAsset/Editor文件夹放到我们ET中的Editor文件夹

Assets/XAsset/Runtime文件夹放到我们ET中的ThirdParty,注意移除UI文件夹,因为他是和xasset的官方Demo耦合的

会有一些Updater脚本的报错,但是不要怕,我们接下来解决他

它里面的报错主要是引用的Message Mono类(一个用于显示对话框的类)找不到导致的,所以我们把这部分内容改成用Debug输出或者直接删掉就行了

这里提供一个我的修改版本的

Updater.cs Author:// fjy Copyright (c) 2020 fjy Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to deal// in the Software without restriction, including without limitation the rights// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell// copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN// THE SOFTWARE.

using System;

using System.Collections;

using System.Collections.Generic;

using System.IO;

using UnityEngine;

using UnityEngine.Networking;

namespace libx

{

public enum Step

{

Wait,

Copy,

Coping,

Versions,

Prepared,

Download,

Completed,

}

[RequireComponent(typeof (Downloader))]

public class Updater: MonoBehaviour

{

public Step Step;

public Action ResPreparedCompleted;

public float UpdateProgress;

public bool DevelopmentMode;

public bool EnableVFS = true;

[SerializeField]

private string baseURL = "http://127.0.0.1:7888/DLC/";

private Downloader _downloader;

private string _platform;

private string _savePath;

private List _versions = new List();

public void OnMessage(string msg)

{

Debug.Log(msg);

}

public void OnProgress(float progress)

{

UpdateProgress = progress;

}

private void Awake()

{

_downloader = gameObject.GetComponent();

_downloader.onUpdate = OnUpdate;

_downloader.onFinished = OnComplete;

_savePath = string.Format("{0}/DLC/", Application.persistentDataPath);

_platform = GetPlatformForAssetBundles(Application.platform);

this.Step = Step.Wait;

Assets.updatePath = _savePath;

}

private void OnUpdate(long progress, long size, float speed)

{

OnMessage(string.Format("下载中...{0}/{1}, 速度:{2}",

Downloader.GetDisplaySize(progress),

Downloader.GetDisplaySize(size),

Downloader.GetDisplaySpeed(speed)));

OnProgress(progress * 1f / size);

}

private IEnumerator _checking;

public void StartUpdate()

{

Debug.Log("StartUpdate.Development:" + this.DevelopmentMode);

#if UNITY_EDITOR if (this.DevelopmentMode)

{

Assets.runtimeMode = false;

StartCoroutine(LoadGameScene());

return;

}

#endif

if (_checking != null)

{

StopCoroutine(_checking);

}

_checking = Checking();

StartCoroutine(_checking);

}

private void AddDownload(VFile item)

{

_downloader.AddDownload(GetDownloadURL(item.name), item.name, _savePath + item.name, item.hash, item.len);

}

private void PrepareDownloads()

{

if (this.EnableVFS)

{

var path = string.Format("{0}{1}", _savePath, Versions.Dataname);

if (!File.Exists(path))

{

AddDownload(_versions[0]);

return;

}

Versions.LoadDisk(path);

}

for (var i = 1; i < _versions.Count; i++)

{

var item = _versions[i];

if (Versions.IsNew(string.Format("{0}{1}", _savePath, item.name), item.len, item.hash))

{

AddDownload(item);

}

}

}

private static string GetPlatformForAssetBundles(RuntimePlatform target)

{

// ReSharper disable once SwitchStatementMissingSomeCases switch (target)

{

case RuntimePlatform.Android:

return "Android";

case RuntimePlatform.IPhonePlayer:

return "iOS";

case RuntimePlatform.WebGLPlayer:

return "WebGL";

case RuntimePlatform.WindowsPlayer:

case RuntimePlatform.WindowsEditor:

return "Windows";

case RuntimePlatform.OSXEditor:

case RuntimePlatform.OSXPlayer:

return "iOS"; // OSX default:

return null;

}

}

private string GetDownloadURL(string filename)

{

return string.Format("{0}{1}/{2}", baseURL, _platform, filename);

}

private IEnumerator Checking()

{

if (!Directory.Exists(_savePath))

{

Directory.CreateDirectory(_savePath);

}

this.Step = Step.Copy;

if (this.Step == Step.Copy)

{

yield return RequestCopy();

}

if (this.Step == Step.Coping)

{

var path = _savePath + Versions.Filename + ".tmp";

var versions = Versions.LoadVersions(path);

var basePath = GetStreamingAssetsPath() + "/";

yield return UpdateCopy(versions, basePath);

this.Step = Step.Versions;

}

if (this.Step == Step.Versions)

{

yield return RequestVersions();

}

if (this.Step == Step.Prepared)

{

OnMessage("正在检查版本信息...");

var totalSize = _downloader.size;

if (totalSize > 0)

{

Debug.Log($"发现内容更新,总计需要下载 {Downloader.GetDisplaySize(totalSize)} 内容");

_downloader.StartDownload();

this.Step = Step.Download;

}

else

{

OnComplete();

}

}

}

private IEnumerator RequestVersions()

{

OnMessage("正在获取版本信息...");

if (Application.internetReachability == NetworkReachability.NotReachable)

{

Debug.LogError("请检查网络连接状态");

yield break;

}

var request = UnityWebRequest.Get(GetDownloadURL(Versions.Filename));

request.downloadHandler = new DownloadHandlerFile(_savePath + Versions.Filename);

yield return request.SendWebRequest();

var error = request.error;

request.Dispose();

if (!string.IsNullOrEmpty(error))

{

Debug.LogError($"获取服务器版本失败:{error}");

yield break;

}

try

{

_versions = Versions.LoadVersions(_savePath + Versions.Filename, true);

if (_versions.Count > 0)

{

PrepareDownloads();

this.Step = Step.Prepared;

}

else

{

OnComplete();

}

}

catch (Exception e)

{

Debug.LogException(e);

Debug.LogError("版本文件加载失败");

}

}

private static string GetStreamingAssetsPath()

{

if (Application.platform == RuntimePlatform.Android)

{

return Application.streamingAssetsPath;

}

if (Application.platform == RuntimePlatform.WindowsPlayer ||

Application.platform == RuntimePlatform.WindowsEditor)

{

return "file:///" + Application.streamingAssetsPath;

}

return "file://" + Application.streamingAssetsPath;

}

private IEnumerator RequestCopy()

{

var v1 = Versions.LoadVersion(_savePath + Versions.Filename);

var basePath = GetStreamingAssetsPath() + "/";

var request = UnityWebRequest.Get(basePath + Versions.Filename);

var path = _savePath + Versions.Filename + ".tmp";

request.downloadHandler = new DownloadHandlerFile(path);

yield return request.SendWebRequest();

if (string.IsNullOrEmpty(request.error))

{

var v2 = Versions.LoadVersion(path);

if (v2 > v1)

{

Debug.Log("将资源解压到本地");

this.Step = Step.Coping;

}

else

{

Versions.LoadVersions(path);

this.Step = Step.Versions;

}

}

else

{

this.Step = Step.Versions;

}

request.Dispose();

}

private IEnumerator UpdateCopy(IList versions, string basePath)

{

var version = versions[0];

if (version.name.Equals(Versions.Dataname))

{

var request = UnityWebRequest.Get(basePath + version.name);

request.downloadHandler = new DownloadHandlerFile(_savePath + version.name);

var req = request.SendWebRequest();

while (!req.isDone)

{

OnMessage("正在复制文件");

OnProgress(req.progress);

yield return null;

}

request.Dispose();

}

else

{

for (var index = 0; index < versions.Count; index++)

{

var item = versions[index];

var request = UnityWebRequest.Get(basePath + item.name);

request.downloadHandler = new DownloadHandlerFile(_savePath + item.name);

yield return request.SendWebRequest();

request.Dispose();

OnMessage(string.Format("正在复制文件:{0}/{1}", index, versions.Count));

OnProgress(index * 1f / versions.Count);

}

}

}

private void OnComplete()

{

if (this.EnableVFS)

{

var dataPath = _savePath + Versions.Dataname;

var downloads = _downloader.downloads;

if (downloads.Count > 0 && File.Exists(dataPath))

{

OnMessage("更新本地版本信息");

var files = new List(downloads.Count);

foreach (var download in downloads)

{

files.Add(new VFile { name = download.name, hash = download.hash, len = download.len, });

}

var file = files[0];

if (!file.name.Equals(Versions.Dataname))

{

Versions.UpdateDisk(dataPath, files);

}

}

Versions.LoadDisk(dataPath);

}

OnProgress(1);

OnMessage($"更新完成,版本号:{Versions.LoadVersion(_savePath + Versions.Filename)}");

StartCoroutine(LoadGameScene());

}

private IEnumerator LoadGameScene()

{

OnMessage("正在初始化");

var init = Assets.Initialize();

yield return init;

this.Step = Step.Completed;

if (string.IsNullOrEmpty(init.error))

{

init.Release();

OnProgress(0);

OnMessage("加载游戏场景");

ResPreparedCompleted?.Invoke();

}

else

{

init.Release();

Debug.LogError($"初始化异常错误:{init.error},请联系技术支持");

}

}

}

}

最后因为我们Model层会用到xasset,所以引用asmdef文件,Hotfix同理

替换ET资源管理模块

因为我们使用xasset全盘托管资源管理(资源加载,热更新),所以我们只需要对其进行封装即可

移除所有打包模块

Editor下的打包模块相关代码都可以删除

ResourceComponent

支持await/async语法

using libx;

using UnityEngine;

namespace ETModel

{

public class ResourcesComponent: Component

{

#region Assets

/// /// 加载资源,path需要是全路径 /// /// /// /// public T LoadAsset(string path) where T : UnityEngine.Object

{

AssetRequest assetRequest = Assets.LoadAsset(path, typeof (T));

return (T) assetRequest.asset;

}

/// /// 异步加载资源,path需要是全路径 /// /// /// /// public ETTask LoadAssetAsync(string path) where T : UnityEngine.Object

{

ETTaskCompletionSource tcs = new ETTaskCompletionSource();

AssetRequest assetRequest = Assets.LoadAssetAsync(path, typeof (T));

//如果已经加载完成则直接返回结果(适用于编辑器模式下的异步写法和重复加载) if (assetRequest.isDone)

{

tcs.SetResult((T) assetRequest.asset);

return tcs.Task;

}

//+=委托链,否则会导致前面完成委托被覆盖 assetRequest.completed += (arq) => { tcs.SetResult((T) arq.asset); };

return tcs.Task;

}

/// /// 卸载资源,path需要是全路径 /// /// public void UnLoadAsset(string path)

{

Assets.UnloadAsset(path);

}

#endregion

#region Scenes

/// /// 加载场景,path需要是全路径 /// /// /// public ETTask LoadSceneAsync(string path)

{

ETTaskCompletionSource tcs = new ETTaskCompletionSource();

SceneAssetRequest sceneAssetRequest = Assets.LoadSceneAsync(path, false);

sceneAssetRequest.completed = (arq) =>

{

tcs.SetResult(sceneAssetRequest);

};

return tcs.Task;

}

/// /// 卸载场景,path需要是全路径 /// /// public void UnLoadScene(string path)

{

Assets.UnloadScene(path);

}

#endregion }

}

BundleDownloaderComponent

using System;

using System.Collections.Generic;

using System.IO;

using System.Threading.Tasks;

using libx;

using UnityEngine;

namespace ETModel

{

[ObjectSystem]

public class UiBundleDownloaderComponentAwakeSystem: AwakeSystem

{

public override void Awake(BundleDownloaderComponent self)

{

self.Updater = GameObject.FindObjectOfType();

}

}

[ObjectSystem]

public class UiBundleDownloaderComponentSystem: UpdateSystem

{

public override void Update(BundleDownloaderComponent self)

{

if (self.Updater.Step == Step.Completed)

{

self.Tcs.SetResult();

}

}

}

/// /// 封装XAsset Updater /// public class BundleDownloaderComponent: Component

{

public Updater Updater;

public ETTaskCompletionSource Tcs;

public ETTask StartUpdate()

{

Tcs = new ETTaskCompletionSource();

Updater.ResPreparedCompleted = () =>

{

Tcs.SetResult();

};

Updater.StartUpdate();

return Tcs.Task;

}

}

}

ABPathUtilities

因为xasset使用全路径对资源进行加载,所以我们要提供路径拓展

//------------------------------------------------------------// Author: 烟雨迷离半世殇// Mail: 1778139321@qq.com// Data: 2020年10月14日 22:27:15//------------------------------------------------------------

namespace ETModel

{

/// /// AB实用函数集,主要是路径拼接 /// public class ABPathUtilities

{

public static string GetTexturePath(string fileName)

{

return $"Assets/Bundles/Altas/{fileName}.prefab";

}

public static string GetFGUIDesPath(string fileName)

{

return $"Assets/Bundles/FUI/{fileName}.bytes";

}

public static string GetFGUIResPath(string fileName)

{

return $"Assets/Bundles/FUI/{fileName}.png";

}

public static string GetNormalConfigPath(string fileName)

{

return $"Assets/Bundles/Independent/{fileName}.prefab";

}

public static string GetSoundPath(string fileName)

{

return $"Assets/Bundles/Sounds/{fileName}.prefab";

}

public static string GetSkillConfigPath(string fileName)

{

return $"Assets/Bundles/SkillConfigs/{fileName}.prefab";

}

public static string GetUnitPath(string fileName)

{

return $"Assets/Bundles/Unit/{fileName}.prefab";

}

public static string GetScenePath(string fileName)

{

return $"Assets/Scenes/{fileName}.unity";

}

}

}

打包配置

BuildlScript

把脚本中对应路径进行修改即可

public static class BuildScript

{

//打包AB的输出路径 public static string ABOutPutPath = c_RelativeDirPrefix + GetPlatformName();

//前缀 private const string c_RelativeDirPrefix = "../Release/";

//Rules.asset保存路径 private const string c_RulesDir = "Assets/Res/XAsset/Rules.asset";

....

}

Assets

按需求修改Manifest保存路径即可

public sealed class Assets: MonoBehaviour

{

public static readonly string ManifestAsset = "Assets/Res/XAsset/Manifest.asset";

...

}

适配FGUI

我们使用FGUI提供的自定义Package加载方式

using System;

using System.Collections.Generic;

using System.Threading.Tasks;

using FairyGUI;

using libx;

using UnityEngine;

namespace ETModel

{

/// /// 管理所有UI Package /// public class FUIPackageComponent: Component

{

public const string FUI_PACKAGE_DIR = "Assets/Bundles/FUI";

private readonly Dictionary packages = new Dictionary();

public void AddPackage(string type)

{

if (this.packages.ContainsKey(type)) return;

UIPackage uiPackage;

if (Define.ResModeIsEditor)

{

uiPackage = UIPackage.AddPackage($"{FUI_PACKAGE_DIR}/{type}");

}

else

{

ResourcesComponent resourcesComponent = Game.Scene.GetComponent();

uiPackage = UIPackage.AddPackage($"{FUI_PACKAGE_DIR}/{type}", (string name, string extension, Type type1, out DestroyMethod method) =>

{

method = DestroyMethod.Unload;

switch (extension)

{

case ".bytes":

{

var req = resourcesComponent.LoadAsset($"{name}{extension}");

return req;

}

case ".png"://如果FGUI导出时没有选择分离通明通道,会因为加载不到!a结尾的Asset而报错,但是不影响运行 {

var req = resourcesComponent.LoadAsset($"{name}{extension}");

return req;

}

}

return null;

});

}

packages.Add(type, uiPackage);

}

public void RemovePackage(string type)

{

UIPackage package;

if (packages.TryGetValue(type, out package))

{

var p = UIPackage.GetByName(package.name);

if (p != null)

{

UIPackage.RemovePackage(package.name);

}

packages.Remove(package.name);

}

if (!Define.ResModeIsEditor)

{

Game.Scene.GetComponent().UnLoadAsset(ABPathUtilities.GetFGUIDesPath($"{type}_fui"));

Game.Scene.GetComponent().UnLoadAsset(ABPathUtilities.GetFGUIResPath($"{type}_atlas0"));

}

}

}

}

热更新流程演示

打包

xasset出包流程为Apply Rule

Build Rule

Build Bundle

Build Player

根据我们ET的传统艺能,资源形式大多都是一个个prefab(但是这种做法不提倡嗷,要按正规项目那样分布)

这里以Unit为例,对Unit文件夹应用Prefab规则(各个规则代表的含义可以去前面链接里的文章查看)

对于FUI(我们的FGUI编辑器导出的文件)需要应用两次规则,因为有png和bytes两种文件

然后我们会得到一个如下所示的Rule.asset文件

其中的Scene In Build选项中需要包含我们随包发布的Scene(ET中的Init.scene)

然后我们Build Bundle,就可以出包了

运行

为Global添加Update Mono脚本

其中各个内容含义为:Step:当前热更新阶段

Update Progess:当前热更新阶段进度

Development Mode:是否开启编辑器资源模式,如果开启会使用AssetDatabase.load进行资源加载原始资源,如果关闭会模拟出包环境下的资源加载

Enable VFS:是否开启VFS(对于VFS更加详细的内容,可以去上文链接中查看)

Base URL:资源下载地址,这里我填写的是HFS的资源地址,如果我们使用ET资源文件服务器就是http://127.0.0.1:8080/

然后在脚本调用,即可进行热更新,其中对于热更新各个阶段的进度,都可对Updater的Step和UpdateProgress来取得

await bundleDownloaderComponent.StartUpdate();

资源加载

同步资源加载

以我们加载Hotfix.dll.bytes为例

GameObject code = Game.Scene.GetComponent().LoadAsset(ABPathUtilities.GetNormalConfigPath("Code"));

byte[] assBytes = code.GetTargetObjectFromRC("Hotfix.dll").bytes;

byte[] pdbBytes = code.GetTargetObjectFromRC("Hotfix.pdb").bytes;

异步资源加载

这里加载一在路径Assets/Textures/TargetTextureName.png中的贴图示例

await Game.Scene.GetComponent().LoadAssetAsync("Assets/Textures/TargetTextureName.png");

资源卸载

Game.Scene.GetComponent().UnLoadAsset("Assets/Textures/TargetTextureName.png");

资源内存释放

xasset采用惰性GC的资源内存管理方案,老版本是每帧都会检查和清理未使用的资源(称为灵敏GC),这个版本底层只会在切换场景或者主动调用Assets.RemoveUnusedAssets();的时候才会清理未使用的资源,这样用户可以按需调整资源回收的频率,在没有内存压力的时候,不回收可以获得更好的性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值