Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/feifeid47/unity-async-uiframe
简单易用的Unity异步UI框架。 足够轻量,无第三方依赖。 兼容多种资源管理系统(Addressable、YooAssets等)。支持使用HybridCLR热更新。 支持自动引用。 支持对UI面板的销毁控制,使内存优化更方便。 支持子UI,子子UI,子子子UI......
https://github.com/feifeid47/unity-async-uiframe
ugui uiframe uiframework unity unity3d
Last synced: 5 days ago
JSON representation
简单易用的Unity异步UI框架。 足够轻量,无第三方依赖。 兼容多种资源管理系统(Addressable、YooAssets等)。支持使用HybridCLR热更新。 支持自动引用。 支持对UI面板的销毁控制,使内存优化更方便。 支持子UI,子子UI,子子子UI......
- Host: GitHub
- URL: https://github.com/feifeid47/unity-async-uiframe
- Owner: feifeid47
- License: apache-2.0
- Created: 2023-01-01T07:58:30.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2024-04-14T11:19:08.000Z (10 months ago)
- Last Synced: 2025-01-14T11:12:30.530Z (12 days ago)
- Topics: ugui, uiframe, uiframework, unity, unity3d
- Language: C#
- Homepage:
- Size: 738 KB
- Stars: 281
- Watchers: 8
- Forks: 38
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# 特点
```
(1) 一个简单易用的异步UI框架
(2) 兼容多种资源管理系统(Addressable、YooAssets等)
(3) 支持自动引用,暴露在Inspector面板上的字段会自动从Hierarchy面板引用
(4) 支持子UI,子子UI,子子子UI......
(5) 支持自定义脚本模板
(6) 支持对UI面板的销毁控制,使内存优化更方便
(7) 强大的扩展性,可以通过自定义事件,来支持自动事件绑定,例如自动绑定按钮的点击事件
(8) 支持多层UI管理
(9) 内置定时器
(10) 支持自动生成代码
```
# 安装## 方案一
使用git URL
```
https://github.com/feifeid47/Unity-Async-UIFrame.git
```![](./README/install.png)
## 方案二
导入unitypackage
# 如何使用
创建UIFrame预制体,可参考如下结构
Canvas的渲染模式要设置成`屏幕空间-摄像机`
```
--UIFrame (RectTransform、Canvas、CanvasScaler、GraphicRaycaster、UIFrame)
------UICamera (Transform、Camera、AudioListener)
------UILayers (RectTransform)
------EventSystem (Transform、EventSystem、StandaloneInputModule)
```初始化
```C#
private void Awake()
{
// 注册资源请求释放事件
UIFrame.OnAssetRequest += OnAssetRequest;
UIFrame.OnAssetRelease += OnAssetRelease;
// 注册UI卡住事件
// 加载时间超过0.5s后触发UI卡住事件
UIFrame.StuckTime = 0.5f;
UIFrame.OnStuckStart += OnStuckStart;
UIFrame.OnStuckEnd += OnStuckEnd;
}// 资源请求事件,type为UI脚本的类型
// 可以使用Addressables,YooAssets等第三方资源管理系统
private async Task OnAssetRequest(Type type)
{
if (!handles.ContainsKey(type))
{
var handle = Addressables.LoadAssetAsync(type.Name);
await handle.Task;
handles[type] = handle;
}
return handles[type].Result;
}// 资源释放事件
private void OnAssetRelease(Type type)
{
if(handles.ContainsKey(type))
{
handles[type].Release();
handles.Remove(type);
}
}private void OnStuckStart()
{
// UI初始化加时间过长,卡住了,打开转圈面板
}private void OnStuckEnd()
{
// 不卡了,关闭转圈面板
}
```
创建一个UI脚本,继承自UIComponent
并挂到与脚本同名的Prefab中```C#
public class UITestData : UIData
{}
[PanelLayer]
public class UITest : UIComponent
{
[SerializeField] private Image img;
[SerializeField] private Text content;
[SerializeField] private Button close;// 创建时调用,生命周期内只执行一次
protected override async Task OnCreate()
{
// 异步请求资源
var completionSource = new TaskCompletionSource();
var handle = Resources.LoadAsync("sprite");
handle.completed += _ =>
{completionSource.SetResult(handle.asset as Sprite);
};
img.sprite = await completionSource.Task;
}// 绑定事件
protected override void OnBind()
{
close.onClick.AddListener(OnClose);
}// 解绑事件
protected override void OnUnbind()
{
close.onClick.RemoveListener(OnClose);
}// 刷新
protected override async Task OnRefresh()
{
// 异步请求网络数据
var completionSource = new TaskCompletionSource();
using var request = UnityWebRequest.Get("http://xxxx");
request.SendWebRequest().completed += _ =>
{
completionSource.SetResult(request.downloadHandler.text);
};
var data = await completionSource.Task;
content.text = data;
}// 显示时调用
protected override void OnShow()
{}
// 隐藏时调用
protected override void OnHide()
{
}// 销毁时调用,生命周期内只执行一次
protected override void OnDied()
{
}private void OnClose()
{
// 关闭当前面板
UIFrame.Hide(this);
}
}
```
使用`[PanelLayer]`或`[WindowLayer]`或继承自[UILayer]的类来标记UI
使用`[PanelLayer]`属性标记的UI类一般用作全屏面板,将由栈进行控制,显示下一个Panel时会将当前Panel关闭,隐藏当前Panel时会显示上一个Panel
使用`[WindowLayer]`属性标记的UI类一般用作弹窗,它显示在Panel之上
一个UI只能标记一个层级属性
可以自定义层级,需要继承自[UILayer],例如BattleLayer用来显示战斗相关UI,NewbieLayer用来显示新手引导相关UI```C#
// 显示UI
UIFrame.Show(new TestUIData());
// 显示子UI
UIFrame.Show(UIBase uibase);
// 隐藏UI
UIFrame.Hide();
// 隐藏子UI
UIFrame.Hide(UIbase uibase);
// 刷新UI
UIFrame.Refresh();
// 刷新子UI
UIFrame.Refresh(UIBase uibase);
// 释放资源
UIFrame.Release();
// 实例化UI资源
UIFrame.Instantiate(gameObject,parent);
// 销毁UI资源
UIFrame.Destroy(gameObject);
// 销毁UI资源
UIFrame.DestroyImmediate(gameObject);```
# UIBase生命周期以下是调用UIFrame.Show显示下一个Panel时的执行过程
![](./README/lifecycle.png)
以UITest为例,TestUI继承自`UIComponent`,当显示UITest时,将按以下步骤依次执行
```
(1) UITest.OnCreate
(2) UITest下所有继承自UIBase组件的OnCreate
(3) UITest.OnRefresh
(4) UITest下所有继承自UIBase组件且激活的物体的OnRefresh
(5) UITest.OnBind
(6) UITest下所有继承自UIBase组件且激活的物体的OnBind
(7) UITest.OnShow
(8) UITest下所有继承自UIBase组件且激活的物体的OnShow
```
隐藏TestUI时,将按以下步骤依次执行
```
(1) UITest下所有继承自UIBase组件且激活的物体的OnUnbind
(2) UITest.OnUnbind
(3) UITest下所有继承自UIBase组件且激活的物体的OnHide
(4) UITest.OnHide
(5) UITest下所有继承自UIBase组件的OnDied
(6) UITest.OnDied
```
`OnCreate`方法和`OnDied`生命周期内只执行一次
只有当`OnCreate`,和`OnRefresh`执行完成后,物体才会被激活,即MonoBehaviour的`Awake`在OnCreate和OnRefresh之后执行
不推荐使用MonoBehaviour生命周期内的函数
需要注意的是,UI的事件绑定和解绑请务必放到`OnBind`和`OnUnbind`中,以避免异步过程中造成的多次响应带来不可预知的错误。在异步过程中,UI会停止响应,如果响应时间超过了`UIFrame.StuckTime`将会触发卡住事件。# 自动引用
首先创建`UIFrameSetting`,右键菜单 -> 创建 -> UIFrame -> UIFrameSetting
可以将`UIFrameSetting`这个文件放到其他位置,而不是必须要在Assets目录下
开启UIFrameSetting中的`Auto Reference`
如果要禁用自动引用,只需关闭UIFrameSetting中的`Auto Reference`
如下:```C#
[SerializeField] private UIRed uiRed;
[SerializeField] private UIBlue uiBlue;
[SerializeField] private Button btnRed;
[SerializeField] private Button btnBlue;
[SerializeField] private Button btnBack;
[SerializeField] private List listImg;
```
只需将Hierarchy要自动引用的物体的名称改成字段的名称(不区分大小写),并且以@开头
改完名称后不需要其他任何操作,在Prefab保存的时候会自动将Hierarchy面板上的值赋值到Inspector面板上。
对于List类型,元素父物体的名称与List字段名称保持一直即可。
在开启自动引用时,被引用的字段将被控制,你无法删除或将该字段的值修改成其他值![](./README/autoref1.png)
# 自动生成代码
首先准备好已经制作好的prefab(需要使用的节点以@符号开头),如图:
![](./README/codegenerate.png)对prefab右键[创建/UIFrame/UIBase]
![](./README/codegenerate2.png)就可以生成一个以这个prefab命名的UIBase脚本`UITest.cs`
下面就是由这个prefab生成的代码,定义好了所有要使用到的属性,并且按钮还生成了点击事件的方法
```C#
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Feif.UIFramework;namespace Feif.UI
{
public class UITest : UIBase
{
[SerializeField] private SubUI testSubUI;
[SerializeField] private Button testBtn;
[SerializeField] private InputField testInputField;
[SerializeField] private Text testText;
[SerializeField] private Image testImage;
[SerializeField] private RawImage testRawImage;protected override Task OnCreate()
{
return Task.CompletedTask;
}protected override Task OnRefresh()
{
return Task.CompletedTask;
}protected override void OnBind()
{
}protected override void OnUnbind()
{
}protected override void OnShow()
{
}protected override void OnHide()
{
}protected override void OnDied()
{
}[UGUIButtonEvent("@TestBtn")]
protected void OnClickTestBtn()
{
}}
}
```
`自动引用功能`和这个`自动生成代码功能`结合使用,可以简化开发流程,少做一些枯燥的工作。已内置`Button`, `InputField`, `Image`, `RawImage`, `Text`, `UIBase`代码片段生成器,prefab中的节点如果使用了这些组件,且节点名称以@符号开头,可自动生成代码和函数
如果内置的代码片段生成器不够用,可以继承`CodeSnippetGenerator`实现自己的代码生成功能。
代码生成器的类必须在`UIFrame.Editor`程序集中。
你可以将自己写的代码生成器脚本放到UIFrame/Editor/Scripts/CodeSnippetGenerator目录下。
或者放在其他文件夹中,但是需要添加一个程序集引用,引用到`UIFrame.Editor`。如果你的项目使用了TMP,可以参考`UGUITextCodeSnippetGenerator`写一个TMP的代码生成器哦。
# 子UI
有时一个面板上会有多个子面板和一些UI元素,希望能在显示一个UI时,能同时将子面板和UI元素进行初始化和刷新
例如,`UITest`有`UIRed`和`UIBlue`这2个子UI,`UIRed`和`UIBlue`都有一个Text组件,显示Data = xxx
希望在显示`UITest`时对`UIRed`和`UIBlue`进行初始化和刷新,更新Data = xxx的值,并且能通过`UITest`上的2个按钮打开子UI,子UI上带一个关闭按钮,能将自己关闭
![](./README/testui2.png)
`UITest`面板的结构如下
`UITest`挂载`UITest`脚本,引用@UIRed、@UIBlue、@BtnRed、@BtnBlue
`@UIRed`挂载`UIRed`脚本,引用`@DataTxt`、`@BtnClose`
`@UIBlue`挂载`UIBlue`脚本,引用`@DataTxt`、`@BtnClose`
根据UIBase的生命周期,显示UITest时,会同时执行UITest下所有继承自UIBase的组件的方法,且会按顺序执行,执行完父物体的函数才会执行子物体的函数```C#
[PanelLayer]
public class UITest : UIBase
{
[SerializeField] private UIRed uiRed;
[SerializeField] private UIBlue uiBlue;
[SerializeField] private Button btnRed;
[SerializeField] private Button btnBlue;
[SerializeField] private Button btnBack;protected override void OnBind()
{
btnRed.onClick.AddListener(OnBtnRed);
btnBlue.onClick.AddListener(OnBtnBlue);
btnBack.onClick.AddListener(OnBack);
}protected override void OnUnbind()
{
btnRed.onClick.RemoveListener(OnBtnRed);
btnBlue.onClick.RemoveListener(OnBtnBlue);
btnBack.onClick.RemoveListener(OnBack);
}private void OnBtnRed()
{
// 这是显示子UI的正确步骤,错误步骤为:UIFrame.Show(data);
var data = new UIRedData() { Content = "This is UIRed" };
UIFrame.Show(uiRed, data);
}private void OnBtnBlue()
{
var data = new UIBlueData() { Content = "This is UIBlue" };
UIFrame.Show(uiBlue, data);
}private void OnBack()
{
UIFrame.Hide(this);
}
}
```
```C#
public class UIRedData : UIData
{
public string Content;
}public class UIRed : UIComponent
{
[SerializeField] private Text dataTxt;
[SerializeField] private Button btnClose;protected override Task OnRefresh()
{
dataTxt.text = $"Data = {Data.Content}";
return Task.CompletedTask;
}protected override void OnBind()
{
btnClose.onClick.AddListener(OnBtnClose);
}protected override void OnUnbind()
{
btnClose.onClick.RemoveListener(OnBtnClose);
}protected void OnBtnClose()
{
UIFrame.Hide(this);
}
}
```# 自定义脚本模板
首先创建`UIFrameSetting`,右键菜单 -> 创建 -> UIFrame -> UIFrameSetting
可以将`UIFrameSetting`这个文件放到其他位置,而不是必须要在Assets目录下
默认脚本模板在UIFrame/Editor/Resources中,可根据需要修改
将模板文件(.txt)拖放到UIFrameSetting中
文件名将会替换模板文件中的`#SCRIPTNAME#`
![](./README/menu.png)
![](./README/template.png)# UI的销毁控制
![](./README/testui.png)
继承自`UIComponent`的脚本都会在Inspector面板上暴露出`Auto Destroy`属性
当启用`Auto Destroy`时,会在UI不可见时销毁该物体,并释放该物体引用的资源
该字段可以在运行时通过代码来控制```
注意:启用Auto Destroy时,关闭面板后再打开面板会执行OnCreate方法
推荐做法:频繁使用的UI禁用该选项以提升UI的打开速度。不频繁使用的UI,或占内存比较大的UI启用该选项以优化内存
```# 动态创建或销毁UI gameObject
如果要在运行时动态创建UI gameObject,请使用以下方法
```C#
UIFrame.Instantiate(gameObject,parent);
UIFrame.Destroy(gameObject);
UIFrame.DestroyImmediate(gameObject);
```使用UIFrame.Instantiate、UIFrame.Destroy来创建或销毁物体时,会自动补全UIBase中的关系树。
这样能确保动态创建或销毁的物体能正确的被UIFrame所控制。# 自定义事件
按钮事件自动绑定,可以通过注册UIFrame.OnBind和UIFrame.OnUnbind来实现。
自带的UITimer属性和UGUIButtonEvent属性也是通过这种方式实现的,如果需要扩展功能,可以参考这些代码
扩展都可以通过注册`UIFrame.OnCreate`、`UIFrame.OnRefresh`、`UIFrame.OnBind`、`UIFrame.OnUnbind`、`UIFrame.OnShow`、`UIFrame.OnHide`、`UIFrame.OnDied`来实现```C#
public class AutoBindXXX
{
public static void Enable()
{
UIFrame.OnCreate += OnCreate;
UIFrame.OnBind += OnBind;
UIFrame.OnUnbind += OnUnbind;
UIFrame.OnDied += OnDied;
}public static void Disable()
{
UIFrame.OnCreate -= OnCreate;
UIFrame.OnBind -= OnBind;
UIFrame.OnUnbind -= OnUnbind;
UIFrame.OnDied -= OnDied;
}private static void OnCreate(UIBase uibase)
{
// 通过反射获得使用了XXXAttribute的字段或方法
var methods = uibase.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.Where(item => Attribute.IsDefined(item, typeof(XXXAttribute)));
// TODO
}private static void OnBind(UIBase uibase)
{
// 执行绑定
}private static void OnUnbind(UIBase uibase)
{
// 执行解绑
}private static void OnDied(UIBase uibase)
{
binds.Remove(uibase);
}
}
```
# 使用UniTask
首先确保Unity工程已经安装了UniTask插件。
只需在脚本定义符号中添加`USING_UNITASK`即可。
![](./README/unitask.png)