UI框架的基本理解

前言

UI框架是游戏客户端中的一个部分,主要负责各种层层叠叠地显示UI,横跨显示和逻辑。

个人理解中,一个UI框架应该是如何去理解UI开发中各个部分的视角,清晰明确的知道各种交互界面中开发的基本逻辑和认知。

通用功能

这些功能不仅在UI开发中使用,在游戏各个部分都会使用到。

通用基类

通用基类存在的意义是能够给出一个通用的操作流程并进行记录,包括初始化销毁的生命周期管理,事件注册、日志存档的通用功能。

通过使用通用基类可以封装一些初步的功能,不需要逐个实现,例如:

  1. 记录下创建和销毁的时间用于排查和分析使用情况,提供快照、避免泄漏等好处
  2. 制作日志时提供基本信息,能够另一个角度查找和分析日志
  3. 自带事件等通用系统的相关调用和维护、避免每次都要手动维护

事件、日志、定时器、异常报错处理等系统

这些都是跨系统的通用机制,这里不赘述其必要性。

交互输入分级系统

对于纯点击交互的游戏来说一般不会单独用到,或者说一般交互框架中已经处理好了。

但是对于手柄、键盘、陀螺仪等交互功能来说,这个系统非常重要。避免将交互应用到错误的层级从而出现错误效果。

UI模块划分

根据实际项目来看,大多数都会将UI模块分至少两种,完整的功能性和界面中的功能组成部分。这里介绍一下项目的UI模块层级。

场景Scene

一个大的通用性容器,确保每个时刻都只有唯一的功能性场景存在。例如:

  • 主场景 HomeScene
  • 自定义房间 CustomRoomScene
  • 匹配场景 MatchScene
  • 战斗场景 BattleScene

这些通过基本功能的划分确保了UI都是归属于不同功能的,避免了不同场景下出现功能错乱的情况

界面Layer

一个完整的功能性界面,例如:

  • 设置界面
  • 商城界面A、B、C…
  • 角色详情界面

界面是可以互相嵌套的,一个通用的商城界面可以在购买角色时额外显示出一个购买角色。

界面从功能上来说也是一个树形结构,通过界面的层层嵌套来满足各种显示和交互上的需求。

具备通用效果的界面往往会进行复用,例如弹出提示框就是一个通用的界面,在创建时会由创建者进行定制和回调绑定以进行复用。

控件Widget

界面中的基本元素、例如:

  • 按钮、进度条
  • 文本框、图像
  • 下拉框、滑动列表

注意,一般来说控件这一层就不继承基本基类了,避免过多垃圾信息

总结

大体上来说,从整个游戏的视角上来看,即是一个游戏中每个时刻只存在唯一一个当前活跃的场景。

大量的界面以树的形式层层挂载到场景上作为不同的功能模块。

界面中又是以树状的节点组织起来控件的显示和交互。

界面层级

界面之前存在层级关系是为了避免错误地遮挡以妨碍交互。这里简单介绍一下项目中的层级划分。父节点直接为场景的界面根据性质来分属于不同层级

  • DEBUG层:开发中才使用的最高层级,这个层级存在的例如DEBUG按钮、功能界面、DEBUG信息
  • LOADING层:主要是在跨场景时进行遮掩才使用的,不能存在交互且会阻断所有交互
  • 教程层:一般只会在触发按钮教学时使用,通过阻断交互来引导交互的教学
  • 演出层:项目的特殊需求,在某些皮肤获得升级的演出时使用,用来营造仪式感,所以优先级极高
  • 弹出交互层:弹出的交互选择框的层级,例如锁定,二次确认等重要的交互按钮均在这一层
  • 提示层:一般如物品获得提示,通用Tips、升级动效等位于此层级,可以有一定简单交互
  • 功能层:大部分强交互功能性页面所在的层级,如商城购买的全流程等

跳转管理

这个根据项目来说有不同的处理方式。

例如有些项目中以堆栈的形式来处理不同跳转和回归,但是存在环状跳转循环的问题。

在个人项目中一般不涉及这些问题,对于跨度很大的跳转需求往往直接场景重载情况并打开指定页面,不会出现往回跳转等问题。

UI任务队列

UI任务队列是一种通用的结构,用来避免大量类似的信息导致显示上阻挡等问题。

一般来说,是在显示某些页面如获得奖励动效、开始弹窗时出现。因此对于这些功能需要通过队列管理器进行插入而非直接添加页面,做到同级信息一次只显示一个。

队列管理器的涉及界面也一般需要为了支持队列做出通用的基类实现。

队列管理器制作时需要注意要点有:

  1. 支持多队列,单队列的调度肯定具有
  2. 支持外部操作阻塞和开放,保留可控性
  3. 支持封装结束条件,如点击取消或固定时间
  4. 支持超时机制等避免错误阻塞

特殊交互传递

即之前说的,如键盘,手柄,陀螺仪等信息的分级处理,由于项目不存在相关需求和验证,因此只为个人设想。

基本概念

特殊交互信息一般以界面为单位分发,对于需要获取到交互信息按钮的界面,需要手动注册到特殊交互系统(或者在多端输入的绑定时自动注册)。

特殊交互系统会判断出当前获取到交互信息的层级中,哪个是最终的活跃界面并将交互层级分发到对应界面。再根据交互信息的类型和界面中控件本身的劫持关系来决定是否传递到下一层。

  • 手柄按键、键盘按钮等会默认只分发到当前活跃层即可
  • 陀螺仪等往往只作为摇晃的基本功能,一般允许传递

另外也需要在界面创建时传递一些界面信息,如挂载到场景的层级,界面嵌套的层级等关系,以实现默认情况下的权限高低判断。

多端输入

多端输入目前只考虑手柄/键盘特定按键和直接点击按钮的绑定关系,对于滑动等操作场景差距过大,难以适配到一类中,暂不考虑。

对于确定需要适配多端的游戏,按键等基本信息需要封装出特殊按钮。包括了:

  • 按键说明的显示:如X或者空格键
  • 一些只在某些输入方式下显示的指示器
  • 一些界面中的控件本身支持在不同输入模式下显示其中的不同节点

在这个时候,一般对于按钮本身的交互绑定就可以进行特殊处理,例如按钮在绑定点击交互时可以同时制定绑定的按键,对于滑动列表绑定滑动方向和列表项点击事件时也需要同步绑定下。

这部分完全没有实践经验,完全无法确认

数据和表现分离和概述

游戏中,由于不同界面可能复杂性都不同,因此只简单介绍分层设计的一些内容

Model和View分离

最基础的一种情况,数据Model和界面显示View分离开来。

这种基础结构衍生时会逐步变成View单向依赖Model,Model变化时发出事件通知,View自行注册事件进行更新。即View基本单向依赖Model。

初步出现中间层,基本的MVC

由于Model的数据较为原始和直接,View使用时往往需要一些更细致的数据,可能源于多个不同的数据源,这里就出现了基本的中间层概念。

通过dataUtils, manager中的管理函数等作为一个中间层,view直接获得处理好的数据用于显示,UI使用了专用的UI数据。

使用MVVM

中间层ViewModel形成,作为一个种专属的类来隔绝Model和View,阻止View和Model之间的相互调用和通知。

反而是由ViewModel和View绑定后,监听Model的数据变化,随后反馈到View上,这种绑定方式往往是尽可能自动化的。

总结

游戏开发中,对于数据和表现分离的呼声和使用率并没有太高,因为系统复杂度、交互表现逻辑复杂和快速迭代的需求等缘故,导致有些模式虽然可以使用,但是并没有太大的帮助,反而开发过程中徒增了成本。

例如某些UI结构原定开发中需要的显示逻辑可能是要绑定任务目标,但在后续开发中变成了状态的显示,这就导致了各种逻辑反而更难适配和理解。不如简单的ModelView依赖的明确。