本文首发自inspoy的杂七杂八 | 菜鸡inspoy的学习记录php
UI在任何游戏里都是个重要的东西,做为一个程序员咱们暂时先不考虑如何设计UI才好看,优先仍是考虑怎么高效地实现功能。
在不少重度UI的游戏中,UI占的比重常常超过核心玩法,UI变多的时候咱们须要用编辑器来设计UI,用代码生成工具来生成相关的代码,以后咱们只关心如何实现相关的逻辑就好了,Unity的场景文件彻底不用修改,由于全部的UI都是代码控制动态添加移除的。
这里使用MVP的架构来实现,Model为主要以单例形式存在的类,用来存储相关的数据;View为根据UI的Prefab生成出来的代码,人工不修改,纯自动生成;Presenter就是咱们须要写代码逻辑的地方了。html
这样子最大的好处在于,当需求有变化时,咱们只须要在编辑器里调整各个UI控件的布局,而后生成相关代码就好了,并不用修改View相关的代码,只须要考虑游戏逻辑方面的修改就够了。
无论UI怎样多怎样复杂,Unity的Scene文件永远不用修改,多人合做时,最大化地分隔开了不一样人的工做,最大程度上地避免了冲突的发生。git
这是个重点,UGUI的UI事件都是基于Unity的EventSystem的,不过咱们以前已经本身实现了一套适合咱们的事件系统了,这里就经过动态增长组件的方式把Unity.EventSystem的事件转换为咱们本身的事件,这样就能统一处理了
以按钮为例,咱们须要监听它的点按事件,为了将这个事件以咱们SFEvent
的形式发布,我继承UnityEngine.EventSystems.EventTrigger
写了一个自定义组件SFUIEventListener
程序员
public class SFUIEventListener : EventTrigger
{
public SFEventDispatcher dispatcher = null;
public static SFEventDispatcher getDispatcherWithGo(GameObject go) {
// 静态方法,自动建立UI控件的事件派发器
var listener = go.GetComponent<SFUIEventListener>();
if (listener == null)
{
listener = go.AddComponent<SFUIEventListener>();
}
if (listener.dispatcher == null)
{
listener.dispatcher = new SFEventDispatcher(go);
}
return listener.dispatcher;
}
public override void OnPointerClick(PointerEventData) {
// 这里是Unity原生的事件
if (dispatcher != null)
{
dispatcher.dispatchEvent(SFEvent.EVENT_UI_CLICK);
}
}
// more...
};复制代码
固然这里只有V和Pgithub
首先须要用编辑器建立并设计UI,最后保存成Prefab
Prefab的结构是这样的:
架构
Canvas - UICamera - UIRoot
每一个场景只有一个。为了方便,我规定咱们游戏的UI分辨率以1280*720为基准,UI切图素材均参照这个分辨率进行制做。
其中UI绘制的话UGUI支持3中:直接叠加,UI摄像机,做为3D物体被主摄像机渲染。咱们使用单独的UI摄像机来实现,Canvas - Render Mode
选择Screen Space - Camera
,而后再Canvas下面建立一个摄像机子节点,命名为UICamera,并将其设置到Canvas中。
编辑器
如今咱们发现Game窗口如今显示的是UICamera拍摄到的东西,因此要设置一下UICamera的属性,首先Clear Flags
改为Depth only
,默认的选项是要从新绘制天空盒,这样一来比他层级低的摄像机拍摄到的画面就彻底被挡住了。Culling Mask
选择UI
,做用是只让这个摄像机拍摄UI层的内容(Canvas节点下全部的子节点默认都是UI层,能够在Transform组件上面的位置看到Tag和Layer,就能发现Layer都已是UI了)。
ide
Orthographic
,默认的透视投影不能保证UI彻底占满UICamera的镜头
接下来是UIRoot这个节点,这实际上是一个全屏的透明Panel,只不过它的Pos Z
值为500,做用是让UI摄像机稳当地看到UI内容,500这个值是随便填的,只是由于若是PosZ为默认值0的话UICamera会拍不到UI内容,大于一个值(好像是20多)才会正常显示。wordpress
好了废话说完,就能够导出了,导出的工具涉及到编辑器的扩展,暂时还不会弄= =先留个坑,以后再填(不是
最终导出的结果大概是这个样子:函数
public class SFTestView : SFBaseView
{
public Text lblTitle{ get { return m_lblTitle; } }
public Button btnOk { get { return m_btnOk; } }
private Text m_lblTitle;
private Button m_btnOk;
private SFTestPresenter m_presenter;
void Start() {
GameObject lblTitleGO = SFUtils.findChildWithParent(gameObject, "lblTitle");
if (lblTitleGO != null)
{
m_lblTitle = lblTitleGO.GetComponent<Text>();
}
// other widgets
m_presenter = new SFTestPresenter();
m_presenter.initWithView(this);
}
}复制代码
其中SFBaseView
继承自MonoBehavior,提供了一个添加事件监听的公共方法:
public void addEventListener(Component widget, string eventType, SFListenerSelector sel) {
var dispatcher = SFUIEventListener.getDispatcherWithGo(widget.gameObject);
dispatcher.addEventListener(eventType, sel);
}复制代码
最后别忘记把View脚本挂载在Prefab上
Presenter文件会在第一次导出UI View时候一并建立出来,初始默认的内容很是简单:
public class SFTestPresenter
{
SFTestView m_view;
public void initWithView(SFTestView view) {
m_view = view;
}
}复制代码
以后咱们就能够开始写逻辑了,好比咱们想给这个按钮加一个点击事件,当点击时改变lblTitle的文本。很是方便,首先在Presenter类中的initWithView()
方法中添加点击事件监听:
m_view.addEventListener(m_view.btnOk, SFEvent.EVENT_UI_CLICK, onButtonClicked);复制代码
而后实现回调函数
void onBtnClicked(SFEvent e) {
m_view.lblTitle.text = "Button Clicked!";
}复制代码
最后实现添加UI View到场景的操做,这里我用了一个静态变量来存储当前场景的UIRoot节点,这个静态变量属于类SFSceneManager
,这个类挂载在每一个场景的一个空节点(Empty GO)中,在场景加载时找到该场景的UIRoot节点并保存到静态变量中,并在场景卸载时清空静态变量。
public class SFSceneManager : MonoBehaviour
{
static public GameObject uiRoot = null;
void Start() {
var uiRootGO = GameObject.Find("UIRoot");
if (uiRootGO == null)
{
SFUtils.logWarning("当前场景没有找到UIRoot节点");
}
uiRoot = uiRootGO;
}
}复制代码
因而咱们就能够在任何一个地方添加UI了,只须要编写以下代码:
GameObject.Instantiate(vwPrefab, SFSceneManager.uiRoot.transform);复制代码
最后就能够看效果啦
上面贴出的代码片断因为篇幅限制只保留了关键部分,完整的代码可在个人github上找到