« skynet 并发模型的一点改进思路 | 返回首页 | 动态字模的管理 »

游戏 UI 模块的选择

在游戏(包括引擎)开发的过程中,谈及 UI 模块,通常所指有二:

  1. 开发工具所用到的 UI 。
  2. 游戏本身所用到的 UI 。

这两者很多时候都是共用的一个模块,比如之前的 Unity 就直接把引擎开发用的 UI 模块扔给开发者开发游戏使用。但很快,开发者就发现,适合工具开发的 UI 模块,在做游戏时就不那么顺手了。所以就有了第三方 UI 插件的流行,以至于最后又倒逼 Unity 官方重新制作游戏 UI 模块。

开发工具面临的需求和游戏场景面临的需求很不一样:

开发工具需要的时候更好的将内部数据以可视化方式呈现出来,供用户浏览和修改,以适应数据驱动的开发。UI 的呈现需要的一致性,风格统一有利于减少学习成本,同时需要清晰的表达复杂的数据结构。有时还需要将内部数据的变化过程同步的动态呈现,给开发者更直观的感受。

游戏 UI 是游戏过程的情感体验的一部分,外观和交互需要根据游戏设计专门化。它往往并不需要表达游戏内部复杂的数据结构,而是将那些数据以额外面对玩家的形式展现出来。玩家通过界面下达的指令也并非直接对数据的修改,而是以指令流的形式传递过去。另外,HUD 也是很大的一个部分,和 UI 对话框在设计上也有很大的不同。

他们两者之间在技术上的共性其实很小,针对这些共性的技术实现可能也只有几百到上千行代码的规模,远少于差异部分需要的代码量。我比较倾向于把这两个东西分开实现。

关于工具所用的 UI 模块的选择,我的开发生涯中早期使用过 MFC ,.net ,wxwidgets,Qt 。近两年做引擎开发时先选择的 iup ,后来转向 dear imgui 。这里面的选择无非是 Retained mode 和 Immediate mode 之争。在这个问题上,我目前比较赞成 im mode ,认同这篇 blog 的观点 。另外加一句,我觉得 stateless 模式可以减少 bug 。

在这个问题上,今天不想展开谈,这次主要说说游戏 ui 。

为游戏写 UI 模块,我自己动手干过两次,都是十多年前的事情。最早是为大话西游写的,后来因为学习 C++ 的新语法又重写过一版,但没有用在自己开发的游戏中(送给朋友,他倒是用在游戏产品里了)。在此之后,我的开发思路改变,不再追求自己实现,而是考察成熟方案。所以如非必要,就不再新起山头了。

最近做游戏引擎开发,又重新考虑该选择一个怎样的游戏 UI 模块。

首先考虑的是对已经在用的 imgui 做一番改造。试过 Nuklear 和 Dear imgui ,改造过程都不太满意。Dear imgui 在做工具开发上已经非常成熟,但项目已经明确了,聚焦于工具开发而不会考虑游戏 UI ,我稍微动了下手,觉得不应该背道而驰。

在做手机游戏开发的初期(陌陌争霸),我也尝试过自己实现一个简单的 IM mode 的 UI 模块,勉强够用,但似乎也不太好用。对游戏开发不太友好。后来就转向 Retained mode 的库。

前些年 CEGUI 非常流行,这两年似乎不那么活跃了。最近两年也涌现过几个类似的项目,看了一下感觉还是太复杂。这些复杂的项目似乎都奔着工具开发的领域去做,其实我是用不着的,犯不着为了不需要的需求增加项目的复杂度。

UI 模块本质上,实现需要解决的仅仅是一组矩形如何布局的问题。这个问题并不复杂,而且布局问题也有非常简洁的开源实现,不需要从头动手。实现了布局后,把控件顺次绘制出来即可。至于优化,可以探讨的方面很多,但可以列为单独任务。

而从设计角度,就是如何去描述这些控件,可以方便的翻译成布局信息。

最近一段时间最打动我的是 Paradox 的游戏引擎。虽然是闭源的,但因为给群星做汉化 Mod 的缘故,我仔细研究了它。它的 UI 系统是完全可以 Mod 的,全部是文本呈现。给了我许多启发。

首先是 UI 的结构和呈现分离。在这个引擎中,区分了 .gui 和 .gfx 两类文件(其实文件格式是统一的,仅仅是后缀不同)。.gui 描述的是 UI 的结构,可以看成是把 UI 看作是树状层级的一个个矩形区域,描述出每个区域的功能。而这些矩形区域到底如何呈现,是用静态图片,还是有动画效果,又或者直接用线条绘制。以及一些和逻辑无关的表现行为,例如鼠标悬浮时需要有一些特别效果,等等。都单另放在一个 .gfx 文件中描述。这就有点像 html 和 css 的分离,但更简洁一些。

我想找找有没有类似设计的开源 UI 库,没有找到。其实自己实现也不算复杂,核心仅在于如何设计这个数据表达的形式,而这个从 Paradox 的游戏文件中已经可以找到完整的参考了。我前段时间自己设计了一版。但在设计的同时,也在继续寻找潜在可用的开源方案。

除了这些从头设计的开源 UI 库以外,这些年还流行另一种解决方案。那就是借用成熟的 UI 表达形式,为游戏环境重新实现。这些成熟的 UI 方案无非两种:flash 或 HTML/CSS 。这类方案的最大优势就是,开发 UI 的工具成熟,开发者不需要学习新概念。而对于 UI 怎样描述这方面,成熟的文件格式可以少踩很多坑。而另起山头自己设计的格式,虽然更简洁,或许能贴近需求,但难免在需求迭代之后,发现之前的失误。

这其中最为有名的是 Scaleform ,是对 Flash 的重新实现。星际 2 就用了它。不过这是个商业中间件,且已经停止开发。开源的类似品也有一些,不太成熟。考虑到 Flash 本身已经日薄西山,我也不想考虑这个系列。

HTML/CSS 方案最近几年非常流行。最有名的是基于开源 webkit 的各种改造。例如吃鸡就是用的这样的方案。但 webkit 还是过于复杂了,这种复杂性最终的反应就是用于游戏后,性能有问题,且不太好优化。吃鸡的团队就做了大量的改造,可以在某些特定场景下,提升一两个数量级的性能。可见 webkit 直接用于游戏引擎复杂度造成的潜在问题。

另外,Valve2 的 Source 2 引擎中提供的 UI 模块 Panorama UI 也是基于 HTML/CSS 的。这是一个商业闭源产品,但可以找到开源的类似品。很多年前,我关注过一个叫 LibRocket 的库,那个时候还不太完善,后来就停止维护了。最近我发现有人在它的基础上 fork 出来一个叫 RmlUI 的项目,我十分喜欢。裁剪掉了许多不必要的功能,代码做了整理以及一定的优化。明显其设计目的就是嵌入到其它游戏引擎中使用。

昨天我阅读了文档,玩了一下。发现一些 mingw 编译的小问题,顺手提了 PR 。这个项目非常活跃,直到昨天还有新的提交。我选择开源项目的首要考察因素就是维护者的活跃程度。所以暂时会把这个作为重点备选项目,尝试往我们正在开发的引擎中集成。


关于集成,一般的 UI 库都会提供一个抽象层让你对接渲染和系统输入消息。而渲染部分最难的是两个问题,一是如何保证性能,二是字体的管理。

由于大多数成熟的开源项目都是西方人领头的,对汉字这种字符量巨大的字符集没有过多的考虑。把动态烘培字模,限制使用贴图数量的方案优先级都排的比较低。如果不下一番功夫,一般就用巨大的贴图,烘培上所有的汉字了事。例如 imgui 就是这么干的,动态的字体贴图管理这个特性已经在 todo list 里停留了很久了,好像也看不到时间表。

如果游戏只使用一两种字体倒无所谓,想让字体丰富一些,这不是个办法。所以我前段时间静下心好好设计并实现了字体渲染中贴图管理的模块。改天我再写一篇 blog 记录一下。

Comments

CEGUI的设计思路现在也是“布局+皮肤”两部分,布局描述各个UI的盒式布局,皮肤描述各个UI元素的显示样式。这种思路看起来很好用,但是我们的产品经常吐槽要自定义某个按钮的样式的时候,得拷贝一份皮肤配置来修改,对于很多经验不多的开发者来说,也是一个很痛苦的事情。HTML+CSS的方式似乎是目前最为灵活的方案,但这个对于游戏来说过于重度,期望能有个简化版的布局样式系统能真正应用到游戏里。

常年使用FairyGUI,UI重在工具链

@operali
你怕是对div+css、react、vue有什么误解……

UI,
一件看似容易却从来不容易的事,很多人说,游戏制作有很大的工作耗在上面(这里指程序员工作量)
UI 为何困难,以html为例, 用div+css直接制作UI,效率其实甚低(缺少抽象组合能力,所以早期那种单调风格的web), 后来有reactjs,vue 这些结构化UI,大大提高了webUI开发效率和可重用性,大家也倾向于制作高度交互,精美的UI了,
vue用作实现编辑器或是游戏UI困难吗,恐怕没什么差别,
今天的游戏UI变得越来越复杂,也有了很多数据可视化的需求,甚至,我期待编辑器创造也会是游戏乐趣的一部分

@Cloud

感谢百忙之中回答。路线不一样我认为主要是目标的差异。我的目的不是做一个通用的游戏引擎,而是为了写现在手上的游戏,基本上是想到哪写到哪。

您的大部分观点我都是认同的。我也不喜欢很大的东西,也许Reactphysics3d会是一个更好的选择(谢谢推荐,后面会去看下能不能更好的换掉我现在的模块)。bullet方面,我主要说的是bullet2,我看blender内部的代码用的就是2,好像体积和复杂度方面也很小。

对于第三方库的选择问题,我觉得最重要的还是功能性代码的可控和可扩展,bullet2内部使用的一套vector math就已经被我换成我自己使用的mathfu,但是IK方面和骨骼动画的结合现在解决起来也很难受。(librocket内部估计也会被很愉快的换掉。对包体积我觉得我可能有点执念一类的玩意,另外实现同样的功能的代码在codebase里出现多次看着也挺难受的)。对于freetype这种纯数据到数据的处理,我觉得可控性可能不是那么重要(不过他的一些东西也被我砍掉了,因为harfbuzz的交叉编译起来太恶心了)。

对于动态语言的问题。
* 我觉得主要还是我当时太年轻了,这个项目最开始是我上大学的时候开始写的,一开始想的是如何更好的利用到多核更快的加载资源,对此来说js的异步逻辑写起来真的挺舒服的(c++的话我得写future + thread + 某个任务class,没有引用元对象的存在我估计写代码会写的很辛苦)。但是我后面发现js没有操作符重载,自己上手改v8和jsc我估计也改不动,所以才会在多个动态语言之间纠结。最近打算看一下quake3的gameplay通用模块然后再考虑一下。
* 对于lua,我一开始试着用它的协程去解决音频解码的一堆坑爹回调问题(苹果的AudioToolbox产生数据真的成谜),后面发现好像直接在c++里写用指针取址测量一下用到的最深调用栈大小直接写在c++里可能是个更好的选择。
* 最后琢磨了一下似乎把flutter的fml拆出来直接用c++写算了,也不用考虑写binding的问题。脚本语言可以在真正产生问题的时候再拿出来考虑。

对于shader和图形api。
* shader的话,我觉得我可能没太表述清楚。我认为宏可能已经够用了(至少对于我来说),前面对于slang那些项目的使用也只是尝试。我现在是用一套对于glsl来说添加了include的工具链,写完了一套简单的延迟渲染(前向的透明部分还没做)+ 一堆后处理特效,光照模型也在向pbr改。(前面一直在解决gbuffer部分包含了对于skinned+morphed两种动画存在于同一个mesh的组合这样比较复杂的情况。)
* 在多次试过D3D12\Vulkan\Metal这些玩意的糅合之后,我放弃了,因为我意识到糅合这些玩意的成本远大于我对于这些api单独写一些能更简单描述一个渲染器的方法,然后直接去写渲染器。
* 我觉得对于RHI的模拟和类似ShaderLab的方案对于游戏引擎这个需要生态的方案来说可能是必须的。但是对于我现在来说,单独写一下似乎并没什么。


@foxerzhou

谢谢分享。看下来你的路线和我的差得非常远。我列一下我的选择,可能没什么参考价值。只能代表我的个人想法,没什么对错。

脚本方面,对于我来说,不太可能有 lua 以外的其它选择。其实我并不把 lua 看成是脚本,而承担的是整个开发任务。在和 C/C++ 嵌入协同工作方面,我看不到有任何别的动态语言可以取代。

我觉得在 3d 开发方面,不应该把精力放在动态语言的性能上。所有性能问题最终都能克服的。不能克服的也不会是语言的问题。单说用 lua 开发,不同的写法不同的设计,就可以轻易差 10 倍以上的性能。选用 lua 本质上选择的是 lua 的数据模型:即把所有的数据结构用 lua table 表达,即使是最终用 C/C++ 去操作这些数据,也访问的是 lua table 。这通常不会比访问一个 struct class array 有数量级上的差距。

而原本 3d 游戏,GPU 本来就可能将承载 50% 甚至 80% 的工作,所以不必太看重 CPU 上的开销。把它看作是固有的性能牺牲,会换取到其它价值即可。如果出了性能问题,也不要急于归结到语言上,更不用绕开具体实现,去找 jit 这样的方案。能解决性能问题的,往往是对问题的重新分析,找到适合该开发语言的具体方法重新解决。Lua 留的口是方便你写 C 扩展。但写 C 扩展的前提是对问题的拆分,把热点真正分层剥离出来。

物理库我个人觉得 bullet 已经不错,但还是庞大。我不喜欢复杂的东西。所以我更推荐 Reactphysics3d 。我觉得第三方库的代码规模还是在自己可能把控的范围内会比较好。评判标准是:如果这个库的原作者放弃维护了,你自己有没有兴趣和精力接手下来延续下去。

shader 方面,我暂时接受 bgfx 的方案。可能是我还没有到写复杂的 shader 的时候,只实现了一套简单的 pbr 的流程。因为很多可变的东西是用宏实现的,我们只在工具链上稍微加强了对宏的支持。这个以后发现需要定制更复杂的 DSL 的时候,再考虑一个更完善的方案。btw, 我和 bgfx 作者交流过。他也觉得有个比宏更高级的 DSL 会有用,但是现在并没有强烈的应用上的驱动力去设计这么一个东西。

我自己和朋友私底下写点游戏,参考了一下您引擎的技术方案,这边也有点想法,想和您交流一下,请不吝赐教。

1. 前面也尝试抽象了RHI,也参考了下UE的源码,感觉还是直接写renderer的形式来直接做游戏会比较好用。后面也可以根据各大api的特性做特定优化。当然您这边引擎需要考虑开发者和通用性可能类似ShaderLab的形式更好些。

2. 另外感觉统一shader语言坑也挺大的。。。前面去做spv->msl的时候感觉很多行为都对不上。也尝试过一些包括slang、bgfx、glsl-opt这样的东西,最后在看smaa的代码的时候,看到他用宏的形式去做了抹平,尝试了一下感觉自己写游戏还是挺好用的。

3. 脚本选取的方面。
* 首先尝试了tcc,主要是想在后面写逻辑的时候辨明白经常更新的逻辑和变动逻辑之后直接就可以把那部分代码改成编译的,但是纯c代码写的很长还是太痛苦了(见vulkan)。。。
* 用过js,前面用用FloatArray/Int32Array和函数ID的模拟堆栈的形式写context在单线程里做Box2D和WebGL的更新,感觉效率还可以,后面感觉iOS上必须要用jsc,android上不用v8开jit感觉还是亏了,这样得写两层binding或者把两个vm的api抹平感觉还是很麻烦。
* python我以前写的很多科学计算,感觉在库扩展方面很好,一些Profiler图片什么的生成一下都很轻松,但是带一堆包什么的还是算了,不知道是不是错觉,感觉blender的py-api就不是那么好用。也看了下AS3和avm2,他也支持AOT了,暂时观望。另外感觉as的使用链路太长了,。
* 对比下来,感觉还是js可能会比较方便(尝试玩过quickjs,不知道能不能直接用在工程里),感觉做一些polyfill一些写ui的形式都可以直接拿来用(不知道redux的状态模式拿来写游戏逻辑会不会很好用),虽然网页这边ui很大一部分都是adobe的延续(mxml和jsx差的真的不大。flex感觉甚至更好用一点。),但是js的异步更新(promise、await)这些写ui和逻辑还是更舒服。

4. 音频
* 这地方感觉现有的东西都没什么比较好用的。所以直接Apple设备用CoreAudio、Win上直接用WinMM,Android上OpenSLES(也许可以在Api-21以上用AAudio替代?)去做系统级别的播放节点。然后写个resampler混个音,转换一下psm数据格式。codec方面,不知道opus会不会比ogg更好用一点,不过听说主要是rtc方面的编码和人声的效果更好,如果存在配音的数据包的话,估摸着应该更好用。
* 尝试过OpenAL和android上的OpenMAX,感觉对于游戏来说都不是很好用。
* 游戏方面很多都是需要重用的效果音,还有就是一个基本不会变的bgm,做的更好一点大概还要把hrtf加上,像是一些ns、aes类的效果我觉得作用反而不是很重要。OpenAL以OpenGL的形式写起来感觉也很麻烦。
* 另外自己做这个感觉以流式去做音频数据decode也很舒服,内存占用能少不少。甚至于网络传输也可以边传边播放。

5. ui算是个麻烦事。。。
* nuklear和OpenGL基本绑死了。
* NanoGUI写起来代码量感觉要死,c++ lambda的形式直接让我现有逻辑涨了将近3M,最坑的还得编译。嵌入已有项目改起来也挺麻烦,因为和GLFW也绑死了。
* imgui倒是很好用,但是如您所说,ui定制化方面和字体方面非常难受,看了看代码结合了一下android的libhwui的字体处理,感觉工作量要炸了,拿来做调试ui感觉还是挺不错的,而且他本来也是这个定位。
* webkit改的吐血。。。甚至于后面甚至想我们本来就是私底下做着玩玩,把opera presto的泄漏代码拿来改改,但是发现他那一套给予yaml的客户端代码改起来也很要命。。。
* netsurf看代码也是看的很难受。。。
* gnome的那一套东西都是基于glib的,他们甚至搞出来vala来写这个,c代码混着宏,感觉又回到了最开始我看blender愚蠢的想把blender-ui代码拆出来的日子。
* 最后甚至于前段时间还想试试flutter,一看它的ui代码全在dart里,随后就完全不想看了。
* librocket是很久以前发现的,但是因为我手头主力开发都是在osx下完成的。Apple又在10.14以后完全干掉了32bit,导致我根本跑不起来用Carbon写的librocket的例子。自己用libgumbo + libcorco搭了一套,感觉自己写需要的标签往上加东西也很麻烦。最后狠心用glfw换掉了librocket里shell的实现,感觉还是好用。现在先用固定管线画着,后面再用nanovg换掉他的渲染底层。RmiUI我看了一下,多了动画我觉得是很好,但是感觉后面对librocket熟悉了之后可以用lottie去做ui动画甚至一些简单的2D tween动画跟舒服点,美工对ae也更熟悉一点。甚至于后面还可以把facebook的libyoga加进来,支持flexbox的排版。不过感觉css2 百分比 + 像素偏移的形式去做可能大多数场景应够用了。

6. 物理
* ode和bullet,感觉似乎都是很好的选择,3D物理的底层我还真没写过,但是看操作模式似乎和box2d差不太多。
* 反向动力学方面,因为狠下心来把libfbxsdk的skinned、morphed动画都按着它demo拆出来用shader画出来了,感觉问题也不算太大。
* 另外看了叶劲峰大佬《爱丽丝疯狂回归》的海飞丝发丝效果,确实挺想写出来试试。

7. 粒子
* 这个Houdini最强,但是他的脚本完全没写过。fbx的粒子部分并不能导出。
* 另外打算看一下UEViewer这个项目能不能直接从ue里提出来粒子。
* u3d的粒子信息是yaml封装的,感觉要完全还原还是挺麻烦的。
* 最后 https://github.com/fredakilla/spkgen 这个粒子编辑器感觉还挺小的,qt写的界面,感觉扩充一下似乎还是可以用的。

总的来说,感觉还是写游戏的时候感觉缺了哪,就去加哪的代码。。。有一段时间美工大大比较忙,我完全脱离游戏去写功能感觉完全不知道在写什么。。。

如果云凤大佬您看我废话到了这里,真的感谢= =,如果有什么意见能提出来让我少走一点弯路就更好了。。。

P.S. 大概触景生情看您写的文章有感于此,废话的有点多了。
最后吐个槽,stellaris的资源一个一个分开的,甚至于音效也是。。。游戏加载好慢。。。

真巧,我刚把librocket的代码逻辑分块,改了glfw在osx上跑起来。但是他底层应该还需要些抽象,打算接上nanovg然后换掉它那个功能有限的render interface。

就等云风大哥的游戏引擎一统国内游戏客户端开源引擎了
光大Lua

这引擎有点666 嘿嘿

云凤大哥是否有了解过FairyGUI?无论开发游戏还是开发工具,都能胜任。甚至还有纯lua的sdk。
另外,字体动态贴图这个,在FairyGUI for threejs里有一个简单的实现,为了效率,只能使用简单的bin packing算法,所以贴图使用率不高,但rgb每个通道都可以用来画,等于3倍的使用率,解决了这个问题。

风哥这引擎啥时候能出来,甚是期待。

贴图、背景音乐太多,也是麻烦

Unity最新推的UIElement就是类似的html+css结构,而且准备editor+runtime通吃

云大既然用过wxwidgets,那么直接用xrc文件来描述是不是就可以了?

“布局+修饰+逻辑行为”好用。

Post a comment

非这个主题相关的留言请到:留言本