Unity-代码分离的UI系统
代码与资源分离是游戏程序设计的核心思想之一,被广大游戏公司多采用,相比于乱成一团的编码方式,它至少有一下几点优势
1.在游戏公司里,美术人员负责界面的设计和制作,程序人员负责界面功能的实现。代码分离有利于美术人员和程序人员的分工合作,两者互相配合,有互不干扰。
2.有利于代码的重复使用,功能相同但外观不同的界面只要一套代码即可。
3.为游戏的热更新提供可能性,若游戏需要更新界面团,只需要下载新的界面资源即可。
面板系统的设计
这套界面系统有面板基类(PanelBase)、面板管理器(PanelMgr)和多个具体的面板组成。所有面板都继承自PanelBase,而PanelMgr提供打开某个面板、关闭某个面板的方法。
先制作两个面板作为例子一个是开始界面(TitlePanel),另一个是游戏介绍(InfoPanel),如下图.
把这两个面板做成预设,放入Resources文件夹下。
面板基类的实现(PanelBase)
PanelBase是一个面板基类,所有的面板逻辑都要继承它,一些设计要点如下。
1.面板的资源(如上面做成的两个面板预设)称为皮肤(skin,为GameObject类型),皮肤的路径称为skinPath。面板管理器会根据skinPath去实例化skin。
2.某些面板有层级关系,比如提示框总要覆盖普通面板。可定义PanelLayer类型的枚举,来指定面板的层级。
3.某些面板需要通过参数来确定他的表现形式。比如提示框,显示的内容由调用它的语句来指定。定义Object类型的变量args可用于接收PanelMgr传来的参数。
下图为面板的生命周期。
阶段 | 说明 |
---|---|
Init | Init是面板初始化阶段,用于处理args参数 |
OnShowing | 面板显示前将会触发OnShowing,可用于处理面板中的监听事件 |
OnShowed | 面板显示后将会触发OnShowed |
Update | 每帧更新 |
OnClosing | 关闭面板前调用OnClosing |
OnClosed | 面板关闭后调用OnClosed |
代码(PanelBase)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PanelBase : MonoBehaviour
{
//皮肤路径
public string skinPath;
//皮肤
public GameObject skin;
//层级
public PanelLayer layer;
//面板参数
public object[] args;
#region 生命周期
//初始化
public virtual void Init(params object[] args)
{
this.args = args;
}
//开始面板前
public virtual void OnShowing() { }
//显示面板后
public virtual void OnShowed() { }
//帧更新
public virtual void Update() { }
//关闭前
public virtual void OnClosing() { }
//关闭后
public virtual void OnClosed() { }
#endregion
#region 操作
protected virtual void Close()
{
string name = this.GetType().ToString();
PanelMgr.instance.ClosePanel(name);
}
#endregion
}
面板管理器PanelMgr
顾明思议,面板管理器的功能就是管理面板,他有一下三个功能。
1.层级管理
2.打开面板
3.关闭面板
在Unity中创建Canvas,并在画布上面添加名为Panel和Tips的两个空物体,有系统的所有面板都会放在这两个空物体下
代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class PanelMgr : MonoBehaviour
{
public static PanelMgr instance;
//画板
private GameObject canvas;
//面板 用于存放已经打开的面板
public Dictionary<string, PanelBase> dict;
//层级 存放各个层级所对应的父物体
private Dictionary<PanelLayer, Transform> layerDict;
//开始
private void Awake()
{
instance = this;
InitLayer();
dict = new Dictionary<string, PanelBase>();
}
private void InitLayer()
{
canvas = GameObject.Find("Canvas");
if (canvas == null)
Debug.LogError("panelMgr.InitLayerfail,Canvas is null");
//各个层级
layerDict = new Dictionary<PanelLayer, Transform>();
foreach(PanelLayer pl in Enum.GetValues(typeof(PanelLayer)))
{
string name = pl.ToString();
Transform transform = canvas.transform.Find(name);
layerDict.Add(pl, transform);
}
}
//打开面板
public void OpenPanel<T>(string skinpath,params object[] args) where T:PanelBase
{
//已经打开
string name = typeof(T).ToString();
if (dict.ContainsKey(name))
return;
//面板脚本
PanelBase panel = canvas.AddComponent<T>();
panel.Init(args);
dict.Add(name, panel);
//加载皮肤
skinpath = (skinpath != "" ? skinpath : panel.skinPath);
GameObject skin = Resources.Load<GameObject>(skinpath);
if (skin == null)
Debug.LogError("PanelMgr.OperPanelfail,skin is null,skinpath= " + skinpath);
panel.skin = (GameObject)Instantiate(skin);
//坐标
Transform skinTrans = panel.skin.transform;
PanelLayer layer = panel.layer;
Transform parent = layerDict[layer];
skinTrans.SetParent(parent, false);
//panel的生命周期
panel.OnShowing();
panel.OnShowed();
}
//关闭面板
public void ClosePanel(string name)
{
PanelBase panel = dict[name];
if (panel == null)
return;
panel.OnClosing();
dict.Remove(name);
panel.OnClosed();
Destroy(panel.skin);
Destroy(panel);
}
}
PanelLayer
public enum PanelLayer
{
Panel,
Tips,
}
上面就是这个Ui系统的基本代码了
下面就拿上面创建好的两个面板为例子。
例子
首先是创建好的TitlePanel面板
创建这样一个脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TitlePanel : PanelBase
{
private Button startBtn;
private Button infoBtn;
#region 生命周期
public override void Init(params object[] args)
{
base.Init(args);
skinPath = "TitlePanel";
layer = PanelLayer.Panel;
}
public override void OnShowing()
{
base.OnShowing();
Transform skintrans = skin.transform;
startBtn = skintrans.Find("btn_Start").GetComponent<Button>();
infoBtn = skintrans.Find("btn_Info").GetComponent<Button>();
startBtn.onClick.AddListener(OnStartClick);
infoBtn.onClick.AddListener(OnInfoClick);
}
#endregion
public void OnStartClick()
{
//开始游戏
//PanelMgr.instance.OpenPanel<OptionPanel>("");
}
public void OnInfoClick()
{
PanelMgr.instance.OpenPanel<InfoPanel>("");
}
}
不需要提前挂在物体上。
对于InfoPanel
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class InfoPanel : PanelBase
{
private Button closeBtn;
#region 生命周期
public override void Init(params object[] args)
{
base.Init(args);
skinPath = "InfoPanel";
layer = PanelLayer.Panel;
}
public override void OnShowing()
{
base.OnShowing();
Transform skintrans = skin.transform;
closeBtn = skintrans.Find("btn_Close").GetComponent<Button>();
closeBtn.onClick.AddListener(OnCloseClick);
}
#endregion
public void OnCloseClick()
{
Close();
}
}
界面系统的调用
最后编写如下脚本调用TitlePanel就行
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Root : MonoBehaviour
{
private void Start()
{
PanelMgr.instance.OpenPanel<TitlePanel>("");
}
}
在场景中创建一个名为Root的空物体挂在Root和PanelMgr
这样就完成了。