fgui的ui管理框架_ET框架FGUIxasset的梦幻联动
前言
技能系統暫時告一段落,現在要花點時間規范一下客戶端這邊的資源管理以及一些流程優化,這里選擇輕量高效資源管理框架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();的時候才會清理未使用的資源,這樣用戶可以按需調整資源回收的頻率,在沒有內存壓力的時候,不回收可以獲得更好的性能。
總結
以上是生活随笔為你收集整理的fgui的ui管理框架_ET框架FGUIxasset的梦幻联动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA如何正确处理Unicode字符
- 下一篇: 前端安全配置之Content-Secur