最近在负责老项目UI的更改,领导说有点乱,确实,改的我真想喝毒药,程序猿应该可以感受到项目重构的痛苦。
之前的UI比较无序吧,就是一个按钮点击去调用某个事件,按钮图标变化,当用其他点击方式调用这个事件后,在去修改按钮图标。两处修改经常会导致显示错乱。还有调用某个事件的时候关闭某个面板,其他函数也有关闭这个面板,都是直接setactive(false),在维护项目的时候是真的很头痛。
于是我就在想我们是否可以设计一套属于我们自己的规则的框架,这样在实现调用的时候我们只需要专心改变一个值即可。而不是到处调用。UI出问题是去年就开始了,会议上我们一致同意使用数据控制显示。去年一直在忙另一个项目,没有时间修改。最近还可以,就彻彻底底在寻找一套适合自己的UI框架。
如上点击某个按钮的时候我们不直接调用事件,而是去修改数据,通过监听数据值的变化来调用绑定的事件。比如:我们点击漫游按钮,事件是:摄像机切换到漫游状态,图标有漫游图标变化为自由视角图标。我们设置一个IsRoaming的变量来保存对应的数据。代码如下:
private static bool isRoaming;
public static bool IsRoaming
{
get { return isRoaming; }
set
{
if (isRoaming != value)
{
isRoaming = value;
if (IsRoamingChanged != null)
IsRoamingChanged(isRoaming);
}
}
}
public static Action<bool> IsRoamingChanged;
然后我们在IsRoamingChanged上绑定我们要做的事件,如下:
UIData.IsRoamingChanged += (bool isOn) =>
{
if (isOn)
{
//切换到漫游状态
//按钮图标变为自由视角图标
}
else
{
//切换到俯视状态
//按钮图标变为漫游图标
}
};
这样,在其他状态下比如点击地面或者其他交互方式切换到漫游状态,可以直接调用UIData.IsRoaming=true;具体会发生什么就不需要我们在关心了,因为我们在开始的时候已经绑定内容了。你可能会觉得有很多变量,这么多数据要一个个写,很麻烦。我们可以编写编辑器脚本辅助我们生成这些数据变量。可能在下一篇博客记录里面。原理就是我们将模板数据对应位置换成我们要创建的变量然后以字符串的形式写入脚本中。
以上结构有个缺点,就是在单纯按钮控制一个面板弹出并且没有其他形式控制面板的时候,代码量会相比传统直接调用方法多。也是我在怀疑自己这个想法的原因。
上面是我之前的想法,因为我觉得使用传统直接调用方法会感觉整个框架不统一。现在想明白了,如果单纯的调用某个功能而没有自身UI的显示变化,我们就直接调用方法,会节省很多时间和代码量。
以上是数据控制方面,而在面板级结构里面,我选择使用的manager of manager。什么意思呢,就是每个面板创建一个对应的xxxpanel,这个panel就相当于面板的manager,负责面板里UI的显示以及按钮的点击。面板都继承自BasePanel,有一些固定的方法如下:
/// <summary>
/// UI面板基类
/// </summary>
public class BasePanel: MonoBehaviour
{
protected UIControler uiControler;
protected RectTransform rectTransform;
[Header("面板初始位置")]
[SerializeField]
protected Vector3 initialposition=new Vector3(241,0,0);//侧边面板初始值
/// <summary>
/// 面板初始化
/// </summary>
public virtual void Init()
{
uiControler = UIControler.GetInstance;
rectTransform = GetComponent<RectTransform>();
transform.transform.SetParent(uiControler.transform);
transform.localScale=Vector3.one;
//位置初始化由子类去实现吧
}
/// <summary>
/// 进入 reset:是否需要重置面板
/// </summary>
public virtual void OnEnter(bool reset=true)
{
gameObject.SetActive(true);
}
/// <summary>
/// 退出
/// </summary>
public virtual void OnExit()
{
gameObject.SetActive(false);
}
/// <summary>
/// 面板重置
/// </summary>
public virtual void Reset()
{
}
}
进入退出初始化等等。(插一句:之所以写OnExit方法,是因为我们在调试的时候发现面板突然被关闭我们一脸蒙蔽的情况下我们可以在OnExit方法中debug,然后通过输出栏找到调用的源头)。而这些所有的面板都被UIController引用。UIController相当于这些panel的领导,panel之间的相互调用要通过UIController,这样也实现了最少引用法,不至于到处引用而后面的人不好维护代码。如果面板都是动态加载的,我们就把加载方法写在UIController中并且加载后保存在里面,我们可以通过PanelType来获取对应的面板,实例化出来后要立即初始化,因为我们可能在Start之前就调用某些方法会发生空引用问题。至于UI点击控制3D中的物体,如果内容很多我们可以专门写一个类来处理,其实就是专事专做,让每个类的分工更明确。
以上就是我对于项目框架的设计构想,还没有经受多个项目的考验,所以现在心里没有太多把握。写在博客上分享出来,一起聊聊。