从我做玄铁引擎的那一天起,我就不停地听到这样的声音,“没有明星游戏产品的实战案例,你如何证明你的引擎是可靠的”;“某某游戏引擎好厉害啊,用它做的某某游戏月流水过千万”;“没有实际游戏项目支撑的游戏引擎,必定都是坑”。
作为一个技术人,听到这样的声音,一开始我是非常诧异的。渐渐地,听得多了,我也就习惯了。这些声音表面看上去,有一定的“道理”,然细思极恐。这反映了一个令人忧心的现状,负责技术选型的人不具备正面评估一款游戏引擎的能力,而只能根据一些侧面信息来评估。
所以,在开始讲玄铁引擎UI实战之前,我想先科普一些UI有关的基础知识,以便读者可以更好的理解,为什么玄铁引擎UI会设计成这个样子。同时,对于有兴趣讨论的同学,也设立一个基本的讨论标准,避免太过主观而毫无依据的论战,甚至是基于立场的骂战。最后,对于正在或者将要设计一款UI框架的人,也会是一个不错的起点。
Immediate Mode GUI
2002年的秋天,在一次分享会上,一位叫CASEY MURATORI的开发者,借用了游戏引擎里常用的"Immediate Mode Rendering"的概念,第一次提出了“Immediate Mode GUI“,传统的GUI则统一称为“Retained Mode GUI“。2005年,CASEY MURATORI写了一篇博客,详细地介绍了他心中的"Immediate Mode GUI“。本文统一将“Immediate Mode GUI“简称为“IMGUI“,而“Retained Mode GUI“则成为“RMGUI“。
https://caseymuratori.com/blog_0001caseymuratori.comIMGUI和RMGUI孰好孰坏,众说纷纭。仁者见仁,智者见智。IMGUI跟RMGUI最大的区别在于,不存储“额外的“,“重复的”状态。比如TextView的Text属性就是一个“额外的”、“重复的”状态。IMGUI不存储Text信息,而是在需要绘制Text的时候,从原始对象中直接获取。这么做的好处是,TextView显示的文本跟原始对象的Text属性永远是一致的。在RMGUI,原始对象的Text变更之后,需要调用TextView::SetText更新TextView的Text属性,否则就产生了不一致。这是RMGUI大部分BUG的根源所在,同时也是一种编程负担。
然而Unity3d告诉我们,IMGUI也有致命的弱点[1],难以进行有效的模块隔离。现有的IMGUI实现采用过程式编程,与RMGUI背道而驰,所有的GUI代码由一个个函数组成,完全抛弃状态,而把所有的状态都放在一个叫做UIContext的对象里,一座新的屎山拔地而起,屹立不倒。IMGUI使用者痛不欲生。
布局系统
现今各种GUI框架使用的布局系统,大致可以分为两种。一种叫Box Layout,一种叫Constraint-based Layout。浏览器和安卓系统使用的布局方案就是典型的Box Layout,而苹果最早引入理念最先进的Constraint-based Layout方案。理论上,Constraint-based Layout是最自由、功能最全面的布局方案,或者说,Constraint-based Layout从理论上统一了布局算法。Box Layout可以说是Constraint-based Layout的子集。
Box Layout
https://www.w3.org/TR/css-box-3/www.w3.orgAndroid Layout
https://developer.android.com/guide/topics/ui/declaring-layoutdeveloper.android.comApple Constraint-based Layout
Understanding Auto Layoutdeveloper.apple.com玄铁引擎的选择
由于Constraint-based Layout实现过于复杂,使用起来也过度烧脑和繁琐,严重依赖编辑器的完善程度。然而Constraint-based Layout的编辑器开发难度非常大[2],最终玄铁引擎选择了Box Layout方案,使用简单、易于理解、更少的配置代码,而且非常适合实现编辑器。
这样,基本的ui背景知识就讲完了,完整的ui框架所涉及知识非常多,这里就不再深入了。有兴趣的童鞋可以在评论区留言。当然啦,感谢三连,喜欢、收藏、点赞。
打地鼠
回归主体,进入实战。
为了完整地展示引擎的能力,接下来我们要做一个打地鼠游戏。由于本文主要介绍玄铁引擎的UI框架,所以本文会用UI系统来做这个打地鼠游戏[3]。
整个游戏可以分为上、中、下和背景四个部分。最上面是顶栏,中间是游戏核心区域,下面是两个按钮。我们从背景开始。
创建一个名为canvas的BoxWidget根节点,用于预览画布有多大。我们希望canvas铺盖整个可视界面,所以在Layout分组中,我们把Horizontal Policy和Vertical Policy都设置为Match Parent,表示匹配父节点大小。在Style分组中,将color设置成#EEEEEE。
效果如下图所示,
为了适应不同的分辨率,我们希望在不同尺寸的屏幕中,游戏画面永远具中切不缩放,我们创建一个名为artboard的BoxWidget,设置如下,
这次,我们将artboard的Horizontal Policy和Vertical Policy设置成Fixed,表示固定大小,同时设置Width和Height属性,这两个属性只会在Fixed时生效。然后,Horizontal Alignment和Vertical Alignment均设为Center,表示在父节点中居中。最后,Color属性设置为#CCCCCC,以便跟canvas的颜色区分开。效果如下图所示,
这时,我们的UI树如下图所示,
接下来,我们放背景图。在artboard节点下,我们再创建一个bg1的ImageWidget。我们希望背景图覆盖整个artboard,所以Horizontal Policy和Vertical Policy均设为Match Parent。在Extension分组中,我们将ImageWidget的Scale Type设置为Fit Inside,表示在保持宽高比的情况下,图片尽可能铺满ImageWidget。Path则设置为背景图片路径。
简单说明一下,为了方便管理资源,玄铁引擎借鉴了Linux文件系统的概念,创建了一个Virtual File System的接口,将不同位置的资源挂载到统一的路径底下。游戏内资源都放在/data/下。
添加了bg1后的效果如下,
接着,我们在artboard底下添加游戏核心区域,名为game的BoxWidget。这次我们设置了game的MarginTop属性,表示与父节点的顶部距离。最后给game设置一个带透明度的Color。完整参数如下,
这时,中间的半透明部分就是我们的游戏核心区域了,
接下来,我们在game节点下,创建一个名为bg2的ImageWidget,用于显示核心部分的背景,效果如下,
此时,背景部分我们就全部完成了。本来打算一个篇幅把整个游戏写完,现在看起来篇幅太长了。还是拆分成几个篇幅比较好。
最后,我们把canvas,artboard,game的绘制关闭,因为我们游戏中我们并不需要。只是在创作过程中用于参考而已。在Inspector中找到最顶部的Widget分组,将Draw Enabled的勾勾取消即可。
最终效果,
最后,我们得到了如下UI树,
相关文章
独立农场主:玄铁引擎UI框架之自动布局 Part 2zhuanlan.zhihu.com=========================================================
欢迎私信、评论交流。感谢喜欢、收藏、点赞。
参考
- ^Unity3d的编辑器UI框架属于IMGUI。
- ^苹果的Xcode Interface Builder至今没有做对过。
- ^正常情况下,游戏性部分不是用UI系统制作的。