欢迎参与讨论,转载请注明出处。
本文转载自https://musoucrow.github.io/2018/10/29/dfq_ui/
前言
在游戏开发的领域里,界面(User Interface)是不可或缺的,在一些强大的游戏引擎会为其配备一套解决方案。和之前的一系列问题一样,LÖVE自然是不会提供的,所以又得自己折腾一套了,本文便记录其中心得。
层级
许多年前,因我年少没经验,写UI都是逐个显示对象并填写参数的。捞的嘛就不谈了,所以也就深刻意识到面向对象以及建立层级体系的重要性。所谓层级体系,也就是将显示对象之间根据需求建立起上下级关系,下级的显示数据会基于上级(如上级移动了坐标,下级也会随之改变),这种玩意在Cocos我称之为Layer-Node体系(如下图所示),在Unity则是以Transform组件实现。欣慰的是,在LÖVE11.0也追加了Transform,同时不幸的是,我用的是旧版本,所以最终是自己造了一遍轮子:(
在设计上我首先实现了集显示与层级管理于一体的对象——Renderer,不过在实现上有点用力过猛,把Shader的层级管理也做了(下级的Shader会与上级的合并)。完成了这个核心之后,再分别设计基本的显示元素(Sprite、Animation、Particle、Label、Layer),不过我并没有另它们继承Renderer,而是设计了一个基类,并将Renderer作为成员对象存在,主要是Renderer的信息量过大,实是不宜直接继承了。结构如下图所示:
焦点
要说UI对象与一般的显示对象最大的不同之处,那便是会接收来自玩家的触控输入了,尤其是作为手机游戏,会同时受到多个触控。再涉及到图层等问题后,便有必要建立一个焦点管理体系了。首先UI对象需要提供判定触控,以及按下、持续、弹起的接口。然后在UIManager提供接收触控输入、焦点管理、对象运行的服务。流程如下图所示:
至于“判定触控所在坐标是否有符合条件的对象”这一需求的实现,便与上文提到的层级体系相得益彰了:显示顺序为从尾到头,而判定自然也是从最顶部的显示对象开始的,于是乎只要将Layer的成员从尾到头遍历判定即可。至于不想参与判定的对象会设置专门属性跳过。
MVC
这个便是老生常谈的设计模式了,去年我也对此作了一篇文章。简要来说便是,UI对象只负责接收输入(Controller)以展示结果(View),UI对象所保存的数据为展示而服务,真正的数据保存在来源对象(Model)。示例如下图所示(这里的Event按照C#的Event去理解即可):
当然以上只是个人的理解,在我看来Controller即Model与View的桥梁,只要符合这个性质的存在即为Controller,它不一定是个固定的形式。包括按钮的按下处理函数这样的存在只要是由外部传入的,那么它也算是Controller。
配置
都8012年了,自然不可能以手写代码的形式创建UI布局,配置化自然是理所当然的:
return {
name = "mapName",
script = "advanced/mapName",
x = 1100,
y = -30,
subject = {
{
name = "bottom",
script = "sprite",
sprite = "mapName"
},
{
name = "label",
script = "label",
font = "normal/18",
color = {
red = 241,
green = 218,
blue = 157,
alpha = 255
},
x = 90,
y = 42
}
}
}
通过这般类似HTML的方式进行编写配置文件,交给专门的创建函数处理即可,具体的数据处理方式则交由对应的类(script)处理。若是开发了相应的UI编辑器还可以直接制作生成配置,很显然这里也是契合了层级体系,可见其重要性。
后记
目前这套界面体系还缺乏相应的编辑器以及没有自适应布局的功能,不过实际上我也不太需要这些。只能说面向内部与面向公众的要求级别是不一样的,所以这并不能代表通用UI库的设计思想,仅供参考而已。