{"id":19534844,"url":"https://github.com/feifeid47/unity-async-uiframe","last_synced_at":"2025-04-07T09:22:28.680Z","repository":{"id":142697593,"uuid":"584072360","full_name":"feifeid47/Unity-Async-UIFrame","owner":"feifeid47","description":"简单易用的Unity异步UI框架。 足够轻量，无第三方依赖。 兼容多种资源管理系统（Addressable、YooAssets等）。支持使用HybridCLR热更新。 支持自动引用。 支持对UI面板的销毁控制，使内存优化更方便。 支持子UI，子子UI，子子子UI......","archived":false,"fork":false,"pushed_at":"2024-04-14T11:19:08.000Z","size":756,"stargazers_count":293,"open_issues_count":8,"forks_count":41,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-03-31T07:09:19.863Z","etag":null,"topics":["ugui","uiframe","uiframework","unity","unity3d"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/feifeid47.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-01T07:58:30.000Z","updated_at":"2025-03-20T02:33:18.000Z","dependencies_parsed_at":"2024-04-14T12:28:06.199Z","dependency_job_id":"d567cd75-f8cf-4336-9438-73b008be00af","html_url":"https://github.com/feifeid47/Unity-Async-UIFrame","commit_stats":{"total_commits":29,"total_committers":2,"mean_commits":14.5,"dds":0.1724137931034483,"last_synced_commit":"1d44a087034af66168e776f48f3b770be569c4ee"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feifeid47%2FUnity-Async-UIFrame","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feifeid47%2FUnity-Async-UIFrame/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feifeid47%2FUnity-Async-UIFrame/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feifeid47%2FUnity-Async-UIFrame/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/feifeid47","download_url":"https://codeload.github.com/feifeid47/Unity-Async-UIFrame/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247622983,"owners_count":20968575,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ugui","uiframe","uiframework","unity","unity3d"],"created_at":"2024-11-11T02:15:51.543Z","updated_at":"2025-04-07T09:22:28.588Z","avatar_url":"https://github.com/feifeid47.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 特点\r\n```\r\n(1) 一个简单易用的异步UI框架  \r\n(2) 兼容多种资源管理系统（Addressable、YooAssets等）  \r\n(3) 支持自动引用，暴露在Inspector面板上的字段会自动从Hierarchy面板引用  \r\n(4) 支持子UI，子子UI，子子子UI......  \r\n(5) 支持自定义脚本模板  \r\n(6) 支持对UI面板的销毁控制，使内存优化更方便  \r\n(7) 强大的扩展性,可以通过自定义事件，来支持自动事件绑定，例如自动绑定按钮的点击事件  \r\n(8) 支持多层UI管理  \r\n(9) 内置定时器  \r\n(10) 支持自动生成代码  \r\n```\r\n# 安装\r\n\r\n## 方案一\r\n\r\n使用git URL\r\n\r\n```\r\nhttps://github.com/feifeid47/Unity-Async-UIFrame.git\r\n```\r\n\r\n\r\n\r\n![](./README/install.png)  \r\n\r\n## 方案二\r\n\r\n导入unitypackage\r\n\r\n# 如何使用\r\n\r\n创建UIFrame预制体，可参考如下结构  \r\nCanvas的渲染模式要设置成`屏幕空间-摄像机`  \r\n```\r\n--UIFrame         (RectTransform、Canvas、CanvasScaler、GraphicRaycaster、UIFrame)  \r\n------UICamera    (Transform、Camera、AudioListener)\r\n------UILayers    (RectTransform)\r\n------EventSystem (Transform、EventSystem、StandaloneInputModule)\r\n```\r\n\r\n初始化\r\n```C#\r\nprivate void Awake()\r\n{\r\n    // 注册资源请求释放事件\r\n    UIFrame.OnAssetRequest += OnAssetRequest;\r\n    UIFrame.OnAssetRelease += OnAssetRelease;\r\n    // 注册UI卡住事件\r\n    // 加载时间超过0.5s后触发UI卡住事件\r\n    UIFrame.StuckTime = 0.5f;\r\n    UIFrame.OnStuckStart += OnStuckStart;\r\n    UIFrame.OnStuckEnd += OnStuckEnd;\r\n}\r\n\r\n// 资源请求事件，type为UI脚本的类型\r\n// 可以使用Addressables，YooAssets等第三方资源管理系统\r\nprivate async Task\u003cGameObject\u003e OnAssetRequest(Type type)\r\n{\r\n    if (!handles.ContainsKey(type))\r\n    {\r\n        var handle = Addressables.LoadAssetAsync\u003cGameObject\u003e(type.Name);\r\n        await handle.Task;\r\n        handles[type] = handle;\r\n    }\r\n    return handles[type].Result;\r\n}\r\n\r\n// 资源释放事件\r\nprivate void OnAssetRelease(Type type)\r\n{\r\n    if(handles.ContainsKey(type))\r\n    {\r\n        handles[type].Release();\r\n        handles.Remove(type);\r\n    }\r\n}\r\n\r\nprivate void OnStuckStart()\r\n{\r\n    // UI初始化加时间过长，卡住了,打开转圈面板\r\n}\r\n\r\nprivate void OnStuckEnd()\r\n{\r\n    // 不卡了，关闭转圈面板\r\n}\r\n```\r\n创建一个UI脚本，继承自UIComponent\u003cT\u003e\r\n并挂到与脚本同名的Prefab中  \r\n\r\n```C#\r\npublic class UITestData : UIData\r\n{\r\n\r\n}\r\n\r\n[PanelLayer]\r\npublic class UITest : UIComponent\u003cUITestData\u003e\r\n{\r\n    [SerializeField] private Image img;\r\n    [SerializeField] private Text content;\r\n    [SerializeField] private Button close;\r\n\r\n    // 创建时调用，生命周期内只执行一次\r\n    protected override async Task OnCreate()\r\n    {\r\n        // 异步请求资源\r\n        var completionSource = new TaskCompletionSource\u003cSprite\u003e();\r\n        var handle = Resources.LoadAsync\u003cSprite\u003e(\"sprite\");\r\n        handle.completed += _ =\u003e\r\n        {\r\n\r\n            completionSource.SetResult(handle.asset as Sprite);\r\n        };\r\n        img.sprite = await completionSource.Task;\r\n    }\r\n\r\n    // 绑定事件\r\n    protected override void OnBind()\r\n    {\r\n        close.onClick.AddListener(OnClose);\r\n    }\r\n\r\n    // 解绑事件\r\n    protected override void OnUnbind()\r\n    {\r\n        close.onClick.RemoveListener(OnClose);\r\n    }\r\n\r\n    // 刷新\r\n    protected override async Task OnRefresh()\r\n    {\r\n        // 异步请求网络数据\r\n        var completionSource = new TaskCompletionSource\u003cstring\u003e();\r\n        using var request = UnityWebRequest.Get(\"http://xxxx\");\r\n        request.SendWebRequest().completed += _ =\u003e\r\n        {\r\n            completionSource.SetResult(request.downloadHandler.text);\r\n        };\r\n        var data = await completionSource.Task;\r\n        content.text = data;\r\n    }\r\n\r\n    // 显示时调用\r\n    protected override void OnShow()\r\n    {\r\n\r\n    }\r\n\r\n    // 隐藏时调用\r\n    protected override void OnHide()\r\n    {\r\n    }\r\n\r\n    // 销毁时调用，生命周期内只执行一次\r\n    protected override void OnDied()\r\n    {\r\n    }\r\n\r\n    private void OnClose()\r\n    {\r\n        // 关闭当前面板\r\n        UIFrame.Hide(this);\r\n    }\r\n}\r\n```\r\n使用`[PanelLayer]`或`[WindowLayer]`或继承自[UILayer]的类来标记UI  \r\n使用`[PanelLayer]`属性标记的UI类一般用作全屏面板，将由栈进行控制，显示下一个Panel时会将当前Panel关闭，隐藏当前Panel时会显示上一个Panel  \r\n使用`[WindowLayer]`属性标记的UI类一般用作弹窗，它显示在Panel之上  \r\n一个UI只能标记一个层级属性  \r\n可以自定义层级，需要继承自[UILayer]，例如BattleLayer用来显示战斗相关UI，NewbieLayer用来显示新手引导相关UI  \r\n\r\n```C#\r\n// 显示UI\r\nUIFrame.Show\u003cTestUI\u003e(new TestUIData());\r\n// 显示子UI\r\nUIFrame.Show(UIBase uibase);\r\n// 隐藏UI\r\nUIFrame.Hide\u003cTestUI\u003e();\r\n// 隐藏子UI\r\nUIFrame.Hide(UIbase uibase);\r\n// 刷新UI\r\nUIFrame.Refresh\u003cTestUI\u003e();\r\n// 刷新子UI\r\nUIFrame.Refresh(UIBase uibase);\r\n// 释放资源\r\nUIFrame.Release();\r\n// 实例化UI资源\r\nUIFrame.Instantiate(gameObject,parent);\r\n// 销毁UI资源\r\nUIFrame.Destroy(gameObject);\r\n// 销毁UI资源\r\nUIFrame.DestroyImmediate(gameObject);\r\n\r\n```\r\n# UIBase生命周期  \r\n\r\n以下是调用UIFrame.Show显示下一个Panel时的执行过程\r\n\r\n![](./README/lifecycle.png)\r\n\r\n\r\n\r\n以UITest为例，TestUI继承自`UIComponent\u003cT\u003e`，当显示UITest时，将按以下步骤依次执行\r\n```\r\n(1) UITest.OnCreate\r\n(2) UITest下所有继承自UIBase组件的OnCreate\r\n(3) UITest.OnRefresh \r\n(4) UITest下所有继承自UIBase组件且激活的物体的OnRefresh\r\n(5) UITest.OnBind\r\n(6) UITest下所有继承自UIBase组件且激活的物体的OnBind\r\n(7) UITest.OnShow\r\n(8) UITest下所有继承自UIBase组件且激活的物体的OnShow\r\n```\r\n隐藏TestUI时，将按以下步骤依次执行  \r\n```\r\n(1) UITest下所有继承自UIBase组件且激活的物体的OnUnbind\r\n(2) UITest.OnUnbind\r\n(3) UITest下所有继承自UIBase组件且激活的物体的OnHide\r\n(4) UITest.OnHide\r\n(5) UITest下所有继承自UIBase组件的OnDied\r\n(6) UITest.OnDied\r\n```\r\n`OnCreate`方法和`OnDied`生命周期内只执行一次\r\n只有当`OnCreate`，和`OnRefresh`执行完成后，物体才会被激活，即MonoBehaviour的`Awake`在OnCreate和OnRefresh之后执行  \r\n不推荐使用MonoBehaviour生命周期内的函数  \r\n需要注意的是，UI的事件绑定和解绑请务必放到`OnBind`和`OnUnbind`中，以避免异步过程中造成的多次响应带来不可预知的错误。在异步过程中，UI会停止响应，如果响应时间超过了`UIFrame.StuckTime`将会触发卡住事件。\r\n\r\n# 自动引用\r\n首先创建`UIFrameSetting`，右键菜单 -\u003e 创建 -\u003e UIFrame -\u003e UIFrameSetting  \r\n可以将`UIFrameSetting`这个文件放到其他位置，而不是必须要在Assets目录下  \r\n开启UIFrameSetting中的`Auto Reference`  \r\n如果要禁用自动引用，只需关闭UIFrameSetting中的`Auto Reference`  \r\n如下：\r\n\r\n```C#\r\n[SerializeField] private UIRed uiRed;\r\n[SerializeField] private UIBlue uiBlue;\r\n[SerializeField] private Button btnRed;\r\n[SerializeField] private Button btnBlue;\r\n[SerializeField] private Button btnBack;\r\n[SerializeField] private List\u003cImage\u003e listImg;\r\n```\r\n只需将Hierarchy要自动引用的物体的名称改成字段的名称（不区分大小写），并且以@开头  \r\n改完名称后不需要其他任何操作，在Prefab保存的时候会自动将Hierarchy面板上的值赋值到Inspector面板上。\r\n对于List类型，元素父物体的名称与List字段名称保持一直即可。  \r\n在开启自动引用时，被引用的字段将被控制，你无法删除或将该字段的值修改成其他值  \r\n\r\n![](./README/autoref1.png)  \r\n\r\n# 自动生成代码  \r\n首先准备好已经制作好的prefab（需要使用的节点以@符号开头），如图：  \r\n![](./README/codegenerate.png)  \r\n\r\n对prefab右键[创建/UIFrame/UIBase]  \r\n![](./README/codegenerate2.png)  \r\n\r\n就可以生成一个以这个prefab命名的UIBase脚本`UITest.cs`  \r\n\r\n下面就是由这个prefab生成的代码，定义好了所有要使用到的属性，并且按钮还生成了点击事件的方法  \r\n\r\n```C#\r\nusing System.Collections;\r\nusing System.Collections.Generic;\r\nusing System.Threading.Tasks;\r\nusing UnityEngine;\r\nusing UnityEngine.UI;\r\nusing Feif.UIFramework;\r\n\r\nnamespace Feif.UI\r\n{\r\n    public class UITest : UIBase\r\n    {\r\n        [SerializeField] private SubUI testSubUI;\r\n        [SerializeField] private Button testBtn;\r\n        [SerializeField] private InputField testInputField;\r\n        [SerializeField] private Text testText;\r\n        [SerializeField] private Image testImage;\r\n        [SerializeField] private RawImage testRawImage;\r\n\r\n        protected override Task OnCreate()\r\n        {\r\n            return Task.CompletedTask;\r\n        }\r\n\r\n        protected override Task OnRefresh()\r\n        {\r\n            return Task.CompletedTask;\r\n        }\r\n\r\n        protected override void OnBind()\r\n        {\r\n        }\r\n\r\n        protected override void OnUnbind()\r\n        {\r\n        }\r\n\r\n        protected override void OnShow()\r\n        {\r\n        }\r\n\r\n        protected override void OnHide()\r\n        {\r\n        }\r\n\r\n        protected override void OnDied()\r\n        {\r\n        }\r\n\r\n        [UGUIButtonEvent(\"@TestBtn\")]\r\n        protected void OnClickTestBtn()\r\n        {\r\n        }\r\n\r\n    }\r\n}\r\n```\r\n`自动引用功能`和这个`自动生成代码功能`结合使用，可以简化开发流程，少做一些枯燥的工作。  \r\n\r\n已内置`Button`, `InputField`, `Image`, `RawImage`, `Text`, `UIBase`代码片段生成器，prefab中的节点如果使用了这些组件，且节点名称以@符号开头，可自动生成代码和函数  \r\n\r\n如果内置的代码片段生成器不够用，可以继承`CodeSnippetGenerator`实现自己的代码生成功能。\r\n\r\n代码生成器的类必须在`UIFrame.Editor`程序集中。  \r\n你可以将自己写的代码生成器脚本放到UIFrame/Editor/Scripts/CodeSnippetGenerator目录下。  \r\n或者放在其他文件夹中，但是需要添加一个程序集引用，引用到`UIFrame.Editor`。  \r\n\r\n如果你的项目使用了TMP，可以参考`UGUITextCodeSnippetGenerator`写一个TMP的代码生成器哦。  \r\n\r\n\r\n# 子UI\r\n有时一个面板上会有多个子面板和一些UI元素，希望能在显示一个UI时，能同时将子面板和UI元素进行初始化和刷新  \r\n例如，`UITest`有`UIRed`和`UIBlue`这2个子UI，`UIRed`和`UIBlue`都有一个Text组件，显示Data = xxx  \r\n希望在显示`UITest`时对`UIRed`和`UIBlue`进行初始化和刷新，更新Data = xxx的值，并且能通过`UITest`上的2个按钮打开子UI，子UI上带一个关闭按钮，能将自己关闭  \r\n![](./README/testui2.png)  \r\n`UITest`面板的结构如下  \r\n`UITest`挂载`UITest`脚本，引用@UIRed、@UIBlue、@BtnRed、@BtnBlue  \r\n`@UIRed`挂载`UIRed`脚本，引用`@DataTxt`、`@BtnClose`  \r\n`@UIBlue`挂载`UIBlue`脚本，引用`@DataTxt`、`@BtnClose`  \r\n根据UIBase的生命周期，显示UITest时，会同时执行UITest下所有继承自UIBase的组件的方法，且会按顺序执行，执行完父物体的函数才会执行子物体的函数\r\n\r\n```C#\r\n[PanelLayer]\r\npublic class UITest : UIBase\r\n{\r\n    [SerializeField] private UIRed uiRed;\r\n    [SerializeField] private UIBlue uiBlue;\r\n    [SerializeField] private Button btnRed;\r\n    [SerializeField] private Button btnBlue;\r\n    [SerializeField] private Button btnBack;\r\n\r\n    protected override void OnBind()\r\n    {\r\n        btnRed.onClick.AddListener(OnBtnRed);\r\n        btnBlue.onClick.AddListener(OnBtnBlue);\r\n        btnBack.onClick.AddListener(OnBack);\r\n    }\r\n\r\n    protected override void OnUnbind()\r\n    {\r\n        btnRed.onClick.RemoveListener(OnBtnRed);\r\n        btnBlue.onClick.RemoveListener(OnBtnBlue);\r\n        btnBack.onClick.RemoveListener(OnBack);\r\n    }\r\n\r\n    private void OnBtnRed()\r\n    {\r\n        // 这是显示子UI的正确步骤，错误步骤为：UIFrame.Show\u003cUIRed\u003e(data);\r\n        var data = new UIRedData() { Content = \"This is UIRed\" };\r\n        UIFrame.Show(uiRed, data);\r\n    }\r\n\r\n    private void OnBtnBlue()\r\n    {\r\n        var data = new UIBlueData() { Content = \"This is UIBlue\" };\r\n        UIFrame.Show(uiBlue, data);\r\n    }\r\n\r\n    private void OnBack()\r\n    {\r\n        UIFrame.Hide(this);\r\n    }\r\n}\r\n```\r\n```C#\r\npublic class UIRedData : UIData\r\n{\r\n    public string Content;\r\n}\r\n\r\npublic class UIRed : UIComponent\u003cUIRedData\u003e\r\n{\r\n    [SerializeField] private Text dataTxt;\r\n    [SerializeField] private Button btnClose;\r\n\r\n    protected override Task OnRefresh()\r\n    {\r\n        dataTxt.text = $\"Data = {Data.Content}\";\r\n        return Task.CompletedTask;\r\n    }\r\n\r\n    protected override void OnBind()\r\n    {\r\n        btnClose.onClick.AddListener(OnBtnClose);\r\n    }\r\n\r\n    protected override void OnUnbind()\r\n    {\r\n        btnClose.onClick.RemoveListener(OnBtnClose);\r\n    }\r\n\r\n    protected void OnBtnClose()\r\n    {\r\n        UIFrame.Hide(this);\r\n    }\r\n}\r\n```\r\n\r\n# 自定义脚本模板\r\n首先创建`UIFrameSetting`，右键菜单 -\u003e 创建 -\u003e UIFrame -\u003e UIFrameSetting  \r\n可以将`UIFrameSetting`这个文件放到其他位置，而不是必须要在Assets目录下  \r\n默认脚本模板在UIFrame/Editor/Resources中，可根据需要修改   \r\n将模板文件(.txt)拖放到UIFrameSetting中    \r\n文件名将会替换模板文件中的`#SCRIPTNAME#`  \r\n![](./README/menu.png)    \r\n![](./README/template.png)  \r\n\r\n# UI的销毁控制\r\n![](./README/testui.png)  \r\n继承自`UIComponent\u003cT\u003e`的脚本都会在Inspector面板上暴露出`Auto Destroy`属性  \r\n当启用`Auto Destroy`时，会在UI不可见时销毁该物体，并释放该物体引用的资源  \r\n该字段可以在运行时通过代码来控制  \r\n\r\n```\r\n注意：启用Auto Destroy时，关闭面板后再打开面板会执行OnCreate方法  \r\n推荐做法：频繁使用的UI禁用该选项以提升UI的打开速度。不频繁使用的UI，或占内存比较大的UI启用该选项以优化内存  \r\n```\r\n\r\n# 动态创建或销毁UI gameObject\r\n\r\n如果要在运行时动态创建UI gameObject，请使用以下方法\r\n\r\n```C#\r\nUIFrame.Instantiate(gameObject,parent);\r\nUIFrame.Destroy(gameObject);\r\nUIFrame.DestroyImmediate(gameObject);\r\n```\r\n\r\n使用UIFrame.Instantiate、UIFrame.Destroy来创建或销毁物体时，会自动补全UIBase中的关系树。  \r\n这样能确保动态创建或销毁的物体能正确的被UIFrame所控制。  \r\n\r\n# 自定义事件\r\n按钮事件自动绑定，可以通过注册UIFrame.OnBind和UIFrame.OnUnbind来实现。  \r\n自带的UITimer属性和UGUIButtonEvent属性也是通过这种方式实现的，如果需要扩展功能，可以参考这些代码  \r\n扩展都可以通过注册`UIFrame.OnCreate`、`UIFrame.OnRefresh`、`UIFrame.OnBind`、`UIFrame.OnUnbind`、`UIFrame.OnShow`、`UIFrame.OnHide`、`UIFrame.OnDied`来实现  \r\n\r\n```C#\r\npublic class AutoBindXXX\r\n{\r\n    public static void Enable()\r\n    {\r\n        UIFrame.OnCreate += OnCreate;\r\n        UIFrame.OnBind += OnBind;\r\n        UIFrame.OnUnbind += OnUnbind;\r\n        UIFrame.OnDied += OnDied;\r\n    }\r\n\r\n    public static void Disable()\r\n    {\r\n        UIFrame.OnCreate -= OnCreate;\r\n        UIFrame.OnBind -= OnBind;\r\n        UIFrame.OnUnbind -= OnUnbind;\r\n        UIFrame.OnDied -= OnDied;\r\n    }\r\n\r\n    private static void OnCreate(UIBase uibase)\r\n    {\r\n        // 通过反射获得使用了XXXAttribute的字段或方法\r\n        var methods = uibase.GetType()\r\n            .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)\r\n            .Where(item =\u003e Attribute.IsDefined(item, typeof(XXXAttribute)));\r\n        // TODO\r\n    }\r\n\r\n    private static void OnBind(UIBase uibase)\r\n    {\r\n        // 执行绑定\r\n    }\r\n\r\n    private static void OnUnbind(UIBase uibase)\r\n    {\r\n        // 执行解绑\r\n    }\r\n\r\n    private static void OnDied(UIBase uibase)\r\n    {\r\n        binds.Remove(uibase);\r\n    }\r\n}\r\n```\r\n# 使用UniTask  \r\n首先确保Unity工程已经安装了UniTask插件。  \r\n只需在脚本定义符号中添加`USING_UNITASK`即可。  \r\n![](./README/unitask.png)  ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeifeid47%2Funity-async-uiframe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffeifeid47%2Funity-async-uiframe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeifeid47%2Funity-async-uiframe/lists"}