UI Kit 是一套界面管理&快速开发解决方案
UI Kit 的特性如下:
UI Kit 本身有一套推荐使用的工作流程,而此工作流程的设计是为了使每个界面只负责展示数据和监听用户输入,界面与界面之间互相独立,并且可独立测试。
下面我们将介绍如何制作一个游戏主页(UIBasicPanel)。
首先我们先创建一个场景:TestUIBasicPanel,如下图所示:

在这里大家要注意一下,UI Kit 推荐每个界面创建一个对应的测试场景,要保证每个界面是可以独立测试的。
接着打开 TestUIBasicPanel 如下所示:

我们拖出来一个 UIRoot prefab,如下所示:

这里非常清晰地可以看到 UI Kit 所支持的所有层级。
接着我们在 Design 层级下创建一个 Panel(右击 Design->UI->Panel) ,并命名为 UIBasicPanel,如下所示:

这里要说一点,Design 层级,顾名思义就是用来做设计的层级,什么是设计?就是拼界面,这个层级就是专门用来拼界面的,Design 层级会在运行的时候会自动隐藏掉自己以及所有的子节点。
OK,接下来,我们将 UIBasic 制作成 prefab,将其放到 Assets/Art/UIPrefabs 目录下,如果没有这个目录就自己手动创建一下。
放入后如下图所示:

Assets/Art/UIPrefab 这个目录是怎么来的呢?它是 QFramework 约定的专门放置 UI 界面 prefab 的位置。而 Assets/Art 是框架推荐存放资源的位置,当然关于资源的存放位置只是推荐,而不是强制的。
但是 UI 界面的 prefab 必须放在 Assets/Art/UIPrefab 目录下,因为这个部分在代码生成的时候需要。
那么有的童鞋可能会问,Assets/Art/UIPrefab 这个路径可以不可以更改?
当然可以,更改的方式也很简单,就是打开包管理面板(QFramework/Preference ctrl + e),打开后可以看到如下面板:

详细的设置方式在上边介绍了,这里就不多介绍了。
接下来需要将 UIHomePanel prefab 标记为 AssetBundle,如下图所示:

标记成功后。
会看到如下结果:

接着,我们在这里要确保一件事情,就是 Res Kit 需要保证当前环境是模拟环境(Simulation Mode),具体看面板中的如下选项是否是勾上即可。

确保勾上之后,我们就开始生成代码,具体操作如下所示(右键->@UI-Kit Create UI Code):

点击之后等待编译,编译结束后,我们看到如下结果:
脚本生成成功

脚本自动挂载了 UIBasicPanel Prefab 上

到此,代码生成部分就介绍完了。
接着,我们想办法让这个场景独立运行。
现在,我们直接运行场景,是不会加载任何界面的,如下所示:

如何让这个场景加载 UIBasicPanel 呢?
很简单,使用 UIPanelTester 如下所示:

按照图中样子设置就好,然后运行场景。
结果如下:

图中成功加载了改界面。
这样,最基本的 UIBasicPanel 测试场景就算搭建完了,同时我们是完全按照 QFramework 推荐的工作流程完成的。
虽然步骤会稍微繁琐一点,但是用一段时间大家就会觉得这是值得的。
OK,接下来我们来介绍控件的自动绑定功能。
我们在 UIBasicPanel 上添加一些按钮,并在每个按钮上挂上 Bind 脚本,如下所示:

接着 Apply UIBasicPanel,如下所示:

这里要注意,一定要选定 UIBasicPanel 再进行 Apply,千万别选成 UIRoot 了。
Apply 之后,再次生成一次代码,操作如下所示:

生成之后,结果如下:

接着,我们打开 UIHomePanel.cs 脚本,试着写一些代码:
using UnityEngine;
using UnityEngine.UI;
using QFramework;
namespace QFramework.Example
{
public class UIBasicPanelData : UIPanelData
{
}
public partial class UIBasicPanel : UIPanel
{
protected override void OnInit(IUIData uiData = null)
{
mData = uiData as UIBasicPanelData ?? new UIBasicPanelData();
BtnStart.onClick.AddListener(() =>
{
Debug.Log("开始游戏");
});
}
protected override void OnOpen(IUIData uiData = null)
{
}
protected override void OnShow()
{
}
protected override void OnHide()
{
}
protected override void OnClose()
{
}
}
}
代码很简单,主要是在 OnInit 的时候注册了 BtnStart 按钮。
接着我们运行场景,接着点击 BtnStart 按钮,得到结果如下:

这样控件自动绑定功能就介绍完了。
自动绑定的功能与 View Controller + Bind 是使用的是同一套机制。
我们运行 UIBasicPanel 是通过 UIPanelTester 实现的。
UIPanelTester 是一个 UI 界面的测试器,它只能在编辑器环境下运行。
真正打开一个 UI 界面,是通过 UIKit.OpenPanel 这个 API 完成的。
只需要写如下代码即可:
UIKit.OpenPanel<UIBasicPanel>();
代码非常简单。
而我们要关闭掉一个 UI 界面也比较容易,代码如下:
UIKit.ClosePanel<UIBasicPanel>();
如果是在一个界面内部关掉自己的话,代码如下:
this.CloseSelf(); // this 继承自 UIPanel
OK,到此我们接触了 3 个 API:
后边的两个没什么好讲的,很简单,但是第一个 API 比较重要,因为它有一些参数我们可以填。
UIKit.OpenPanel 的参数定义及重载如下:
public static T OpenPanel<T>(UILevel canvasLevel = UILevel.Common, IUIData uiData = null,
string assetBundleName = null,
string prefabName = null) where T : UIPanel
{
...
}
public static T OpenPanel<T>(IUIData uiData, PanelOpenType panelOpenType = PanelOpenType.Single,
string assetBundleName = null,
string prefabName = null) where T : UIPanel
{
...
}
public static UIPanel OpenPanel(string panelName, UILevel level = UILevel.Common, string assetBundleName = null)
{
...
}
所有参数如下:
都有默认值,所以这四个参数都可以不用传。
不过这四个 API 在某种情况下非常实用。
下边举一些例子。
// 在 Forward 层级打开
UIKit.OpenPanel<UIBasicPanel>(UILevel.Forward);
// 传递初始数据给 UIHomePanel
UIKit.OpenPanel<UIBasicPanel>(new UIHomePanelData()
{
Coin = 10
});
// 从 UIHomePanelTest.prefab 加载界面
UIKit.OpenPanel<UIBasicPanel>(prefabName: "UIBasicPanel");
都比较容易理解。
有的童鞋可能会问,我们给 UIHomePanel 传递的 UIHomePanelData,在哪里使用呢?
答案是在,OnInit 和 OnOpen 中,如下所示:
namespace QFramework.Example
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
public class UIBasicPanelData : QFramework.UIPanelData
{
public int Coin;
}
public partial class UIBasicPanel : QFramework.UIPanel
{
protected override void OnInit(QFramework.IUIData uiData)
{
mData = uiData as UIBasicPanelData ?? new UIBasicPanelData();
// please add init code here
// 外边传进来的,第一次初始化的时候使用
Debug.Log(mData.Coin);
}
protected override void OnOpen(QFramework.IUIData uiData)
{
// 每次 OpenPanel 的时候使用
Debug.Log((uiData as UIBasicPanelData).Coin);
}
protected override void OnShow()
{
}
protected override void OnHide()
{
}
protected override void OnClose()
{
}
}
}
为什么要这样做呢?
笔者认为,界面有两种显示数据的用法,一种是有的界面是需要从外边填充的,比如警告、弹框、或者道具信息页面等。另一种界面是需要自己获取数据并展示的,比如游戏中的主角金币、等级、经验值等。
如果界面的数据都从外边填充,那么这个界面会拥有更好的可复用性。
当然需要一个可复用性的界面还是需要一个普通界面就看大家的需求了,并不是说有可复用性的界面就是好的。
StartCoroutine(UIKit.OpenPanelAsync<UIHomePanel>());
// 或者
UIKit.OpenPanelAsync<UIHomePanel>().ToAction().Start(this);
在 WebGL 平台上, AssetBundle 加载资源只支持异步加载,所以为此提供了 UIKit 的异步加载支持。
我们先看下 UIBasicPanel 的代码,如下:
namespace QFramework.Example
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
public class UIBasicPanelData : QFramework.UIPanelData
{
}
public partial class UIBasicPanel : QFramework.UIPanel
{
protected override void OnInit(QFramework.IUIData uiData)
{
mData = uiData as UIHomePanelData ?? new UIHomePanelData();
// please add init code here
}
protected override void OnOpen(QFramework.IUIData uiData)
{
}
protected override void OnShow()
{
}
protected override void OnHide()
{
}
protected override void OnClose()
{
}
}
}
默认的生命周期函数如下:
OnInit 则是在 UIPanel 所在的 prefab 初始化的时候进行调用的,在调用 UIKit.OpenPanel 时,只要在 UIKit 中没有对应的缓存界面时,就会调用一次 OnInit 这个周期。
OnOpen 就是每次 UIKit.OpenPanel 调用时,就会调用。
OnShow 实际上调用时机与 UIKit.OpenPanel 是一样的,只不过 OnShow 是最初版本遗留下拉的 API,所以就保留了。当然还有 UIMgr.ShowPanel 调用时,OnShow 会被调用
OnHide 则是在 UIKit.HidePanel 调用时,OnHide 会被调用。
最后 OnClose 就是在 UIKit.ClosePanel 调用时,就会触发,实际上 OnClose 相当于 OnDestory 这个周期。
大概就这些,其中 UIKit.OpenPanel 会触发资源的加载和初始化操作,而 UIKit.ClosePanel 则会触发卸载和销毁操作,只要记得这两点就好。
笔者基本上就只会用到 OnInit 和 OnClose 这些周期,偶尔会用一用 OnOpen。
OK,此篇的内容就这些。
参数定义如下:

对应 UIRoot 上的 Canvas Scaler 如下:

大部分项目,用这个 API 做屏幕适配足够了。
获取 UIRoot 的摄像机。
var uiCamera = UIKit.Root.Camera;
有的时候,UI 需要实现一个 UI 界面的堆栈,以便于支持返回上一页这样的操作。
这个时候就可以用 Push 和 UIPanel.Back 实现。
示例代码:
UIKit.Stack.Push(this); // this 是 Panel
// UIHomePanel 需要确保是打开的状态,如果不打开会报错。
UIKit.Stack.Push<UIHomePanel>();
this.Back(); // 弹出 UIHomePanel
this.Back(); // 弹出 this
非常简单。
在此篇的最开始,笔者手动创建了一套围绕 UIBasicPanel 的测试、开发场景,其过程比较繁琐。
为了解决这个问题,笔者写了一个简单的 UIPanel 自动生成工具。
接下来看下它的基本使用流程。
首先,快捷键 ctrl + e 打开 PackageKit 面板,如下:

在上图中界面名字的输入框中输入 Game/UIGamePanel,然后点击创建 UI Panel,如下所示:

输入之后可以看到即将生成文件的预览。
在这个面板中,我们还可以设置 分辨率与适配对齐,还有模块的目录,如果不想在更目录创建按照规范生成文件,也可以在其他子目录中创建。
我们点击 “创建 UI Panel” 这个按钮。
点击之后结果如下:

相关的 prefab,场景、脚本都生成好了,就连 AssetBundle 也都标记好了,如下:

这就是这个工具的一个用处,非常方便,解决了笔者大量的开发工作量。
在上一篇,我们了解了界面的打开和关闭相关的 API。
在这一篇,我们了解一下 UI Kit 中的 子界面/子控件—UI Element
在前篇,我们了解到,一个 UIPanel 是可以自动绑定几个 子控件的(Bind)。但是当一个界面结构比较复杂的时候,不可能一个 UIPanel 管理数十个 Bind,这时候就需要对 Bind 进行一些打组操作。我们的 UIElement 就可以登场了。
使用方式非常简单,就是将 Bind 中的 标记类型 改成 Element即可,如下所示。


并且要给 生成类名 填写一个名字,这个名字决定生成的类的名字。这里填写了 UIAboutSubPanel。
之后进行 Apply 操作。

注意这里 Apply 的是 UIBasicPanel。
接着生成代码, 如下:

等待编译后,如下所示:

BtnClose 由 UIAboutSubPanel 管理了

我们看下脚本目录:

目录生成了一个新的文件夹,是以父 Panel (UIBasicPanel)为名的。
打开 UIAboutSubPanel 脚本,代码如下所示:
/****************************************************************************
* 2022.7 LIANGXIEWIN
****************************************************************************/
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using QFramework;
namespace QFramework.Example
{
public partial class SubPanel1 : UIElement
{
private void Awake()
{
}
protected override void OnBeforeDestroy()
{
}
}
}
再看下 UILoginView.Designer.cs 脚本,如下所示:
/****************************************************************************
* 2022.7 LIANGXIEWIN
****************************************************************************/
using UnityEngine;
using UnityEngine.UI;
using QFramework;
namespace QFramework.Example
{
public partial class SubPanel1
{
[SerializeField] public UnityEngine.UI.Button BtnStart2;
[SerializeField] public UnityEngine.UI.Button BtnStart3;
public void Clear()
{
BtnStart2 = null;
BtnStart3 = null;
}
public override string ComponentName
{
get { return "SubPanel1";}
}
}
}
结构与之前的 UIBasicPanel 非常相似。
接下来,就可以写一些与子模块相关的逻辑了,关于 UIElement 的基本使用就介绍到这里。
UIKit.OpenPanel<UIMultiPanel>(new UIMultiPanelData(), PanelOpenType.Multiple);
继承 AbstractPanelLoaderPool 类,再实现一个 IPanelLoader 的类,参考代码如下:
using System;
using UnityEngine;
namespace QFramework.Example
{
public class CustomPanelLoaderExample : MonoBehaviour
{
public class ResourcesPanelLoaderPool : AbstractPanelLoaderPool
{
/// <summary>
/// Load Panel from Resources
/// </summary>
public class ResourcesPanelLoader : IPanelLoader
{
private GameObject mPanelPrefab;
public GameObject LoadPanelPrefab(PanelSearchKeys panelSearchKeys)
{
mPanelPrefab = Resources.Load<GameObject>(panelSearchKeys.GameObjName);
return mPanelPrefab;
}
public void LoadPanelPrefabAsync(PanelSearchKeys panelSearchKeys, Action<GameObject> onPanelLoad)
{
var request = Resources.LoadAsync<GameObject>(panelSearchKeys.GameObjName);
request.completed += operation => { onPanelLoad(request.asset as GameObject); };
}
public void Unload()
{
mPanelPrefab = null;
}
}
protected override IPanelLoader CreatePanelLoader()
{
return new ResourcesPanelLoader();
}
}
void Start()
{
// 游戏启动时,设置一次
UIKit.Config.PanelLoaderPool = new ResourcesPanelLoaderPool();
}
}
}
如果想要支持 其他方式加载界面则可以通过此方式定制。
另外,QFramework 中的 UIKit 默认使用 ResKit 的方式加载界面。
可以在 QFramework 源码中看到如下代码:
using System;
using UnityEngine;
namespace QFramework
{
public class UIKitWithResKitInit
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void Init()
{
UIKit.Config.PanelLoaderPool = new ResKitPanelLoaderPool();
}
}
...
}
如果想要使用自定义的方式加载界面,需要将以上代码注释掉。
好了,关于 UIKit 自定义加载界面就简单介绍到这里。
在这一章,UI Kit 的核心功能,我们都接触过了,如下:
只要掌握了以上这些,基本上开发一些界面就没啥问题了。
关于 UIKit 就介绍到这里。
我正在使用i18n从头开始构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在rubyonrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我有一个在Linux服务器上运行的ruby脚本。它不使用rails或任何东西。它基本上是一个命令行ruby脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD