欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

谈谈为什么要用FairlyGUI以及UGUI的自适应与FairlyGUI的自适应

程序员文章站 2022-05-30 17:32:04
...

之前刚来现在这个项目的时候,我就跟我们的主程提过使用FGUI去替代UGUI的事情。 原因有以下:

  1. 可以让策划与美术参与到UI的制作中, 并且贯穿整个游戏开发的全程。 对于游戏项目来说,UI开发是必不可少且十分费时间的一件事情,而且在一个功能模块在完成之后, 逻辑的大改的次数可能不是特别多, 但是UI大改的次数, 我觉得至少都得3次以上。 至少我到现在,没有遇到那个游戏项目的UI的改版是在3次以下。 所以让UI的制作能脱离程序环节是必不可少的。
  2. 上面一点说到UI脱离程序环节的的重要性,可能会有童鞋说,UGUI 和NGUI也可以让策划参与制作啊, 也能让程序不脱离拼UI的苦逼境界。 确实,这点我无法反驳。 但是我可以用实际的例子举例, 我的第二个游戏项目就是使用的Unity, 而且UI是用的UGUI, 那个时候我们使用的Unity的版本是4.6. 我们的UI的制作是全部由策划去拼的,程序不用参与UI制作当中,确实节省了程序大量的时间,但是或多或少还是有一些缺点。说说策划参与UGUI制作我遇到的问题:
    • 但是因为UGUI的节点是由父子关系的, 而且程序在写逻辑代码的时候大概率会依赖于这个UI结构。所以需要策划先去学习Unity的UGUI的层级的概念, 而且还有一些复杂的结构,所以一般情况在策划制作完成UI之后,程序可能都需要简单的修改修改,而且策划同时还要管理Unity的UI的UI的合理规划,所以对于策划来说技术门槛有点高。
    • 当进行UI修改的时候, 难免会修改到UI的层级路径, 因为我们是项目纯lua开发。所以UI逻辑也是lua依靠find写的。所以一旦策划修改层级路径之后,就会出现运行不了的Bug。
    • UGUI的DrawCall的合并依赖于UGUI层级的规划,层级规划的好与不好的情况,一个复杂的UI界面的Drawcall应该会多10-20个。
    • UGUI的重绘也是依赖UGUI的Canvas, 即每次重绘都是整个Canvas一起重绘。所以需要对UI的设计有较好的规划,比如一个复杂的界面是一个单独的Canvas,这样在UI重绘的时候,不会引起其它的界面重绘,但这个与项目是怎么显示和隐藏界面的方法有很大的关系,这里就不展开了。
  3. FGUI是仿照Adobe系列制作的UI以及功能设计,同时也是所见即所得的方式,而且还能够编辑器进行简单的测试。能够很方便的使美术参与到游戏UI的设计修改中, 不仅仅是策划参与,也给予了美术足够高的参与度。这是UGUI不能提供的一个很便利的方式,因为UI美术基本是不可能去学或者说学不会Unity的操作,而且流程确实过于复杂。
  4. FGUI的资源管理很方便,美术只要自己规划的好, 就不会出现资源重复,或者找不到等等问题, 因为编辑器是怎么放置资源,怎么引用资源,公有资源放在哪里,独有资源放哪里,在编辑器中都是一目了然。都很方便,还有其它很多比较方便的功能,需要大家自己去体验和学习。
  5. 还有一个很重要的原因,也是我个人坚决想要推行FGUI的原因。 就是针对于初级开发者来说, 做UI这个活是又脏又累,而且还学不到太多东西, 花的也很多。 但是你说你不做呢, 有不太可能, 因为游戏项目,UI的开发比重其实占比是很大的。最后还是勉为其难的做下去,导致个人觉得自己在干了1-2年之后技术实力还是没有什么提高,最后对UI开发又了很强的抵触心理。 我自己开发UI的时间不长, 除了第一家公司的时候, 全公司基本就我一个写UI, 而且是使用Cocos2dx用C++手写UI之后,就基本没有再写过什么UI模块了, 一般是开发底层框架和开发工具居多。但是这样的事情我看的很多,又很多感受。 我觉得做一个leader,一定要考虑下面兄弟们的感受, 要让有自己在公司工作技术能有所精进, 合理安排工作。

我记得我在和我们Leader在讨论这个问题的时候,我给他讲了一些FGUI的好处, 很明显,他很也意动, 但是因为他可能需要考虑很多其它的问题, 所以也不敢下决定, 所以让我给他演示一下FGUI的一些简单的用法与操作, 做一个UGUI的ScrollRectList无限滚动列表。 很可惜,我自己在尝试使用FGUI并没有专门去研究这个, 我只是看过FGUI的UnitySDK的源码, 觉得代码不错,而且扩展方便, 在使用上至少是一个可控级别的。 所以我没有办法给他简单的展示这个例子, 我告诉他FGUI的例子中有, 但是我估计他是觉得连我都不知道怎么去用,就向他热情的安利, 本来就摇摆的心, 直接就给出了拒绝的答复。 之后项目就开始大量进人手, 在之后就开始使用uGUI进行大量的开发,想要调整,也需要很长的时间,所以也就搁置了这个提议。

其实我个人在他拒绝这个提议的时候,是很伤感的。 为什么呢? 因为在我加入到这个团队的时候, 团队属于初创, 当时才3个人。而且UI的东西完全就没有动起来。 我觉得完全可以拿出一周左右的时间,或者派专人, 或者他自己亲自去调研,体验一下这个东西的利弊, 然后再做决定。 谈起这个,说说我成都的老东家, 当时属于第一次使用Unity做游戏, 也是一个初创团队,我也很有幸再立项之前就加入到了团队中。 也是我在项目中提议使用 当时特别新的技术 ulua 做游戏开发以及基础框架, 同时做热更新。我记得那个时候是14年, 当我提出来之后, CTO,主程还有我们大家都亲自去试验了一把, 最后大家一致决定采用(原来的公司有PC开发背景,使用lua特别多)。 在这里给我们点个赞。 对比起来,现在的项目Leader不够果断。虽然可能有一些客观原因。我个人也是很理解,但是我还是希望这个东西能融进项目中来, 因为这个融入进来之后有一个很大的作用。

最后再说说我自己在搭建自己的框架的时候使用FGUI进行开发的时候,遇到的一些小小困扰:

  1. 首先其冲其实就是不管用什么UI开发游戏都会遇到,适配的问题。在FGUI的官网上, 我看到了FGUI有专门针对Unity的教程,而且教程里面也有一篇专门将适配的文章, 因为我自己之前没有做过适配,也没有太多经验,一直都很疑惑FGUI的适配到底应该去做。 在谈FGUI的适配的时候, 我们简单看看我们在UGUI中是如何去做UI适配的。
    不管是在那个项目中,在UI上面应该都有这么一个概念, 那么就是什么是设计分辨率, 什么是运行分辨率。 举例:
    在我们设计UI的时候, 所有UI的最大尺寸按照 1334x750 去设计。 1334x750 就是我们的设计分辨率。 然后当我们的游戏运行在比如说 Iponex 上面,Iponex的分辨率为: 2436x1125 那么这个2436x1125就是我们的运行分辨率。 这个时候我们的设计分辨率跟运行分辨率就产生不一致, 这个时候我们就需要对我们的界面进行一个整体的缩放,来达到运行分辨率靠近设计分辨率的目的。 在Unity中有一个这个组件Canvas Scaler的组件,这个组件就是用来干这个用的,看下图:
    谈谈为什么要用FairlyGUI以及UGUI的自适应与FairlyGUI的自适应
    图中1的 Reference Resolution: 表示我们的设计分辨率,在这里我填的是1334x750。 Screen Math Mode:表示我们的整体适配的方式。 这里我选的是以宽度或者高度适配, 下面的进度表示宽和高各自在适配中的占比。MathMode还有其他的选项, 一个是Expand: 表示以扩展的形式进行整体适配,这种方式的好处是保证所有的UI元素,全在内部, 但是会留边, 同时Canvas的大小会比设计分辨率的大小大一点。
    谈谈为什么要用FairlyGUI以及UGUI的自适应与FairlyGUI的自适应 一个是Shrink: 表示以缩放的形式进行整体适配。看下图,这种方式不会留边,但是会让UI元素有可能会超出屏幕, 特别是设置为跟随界面扩展的UI元素,同时Canvas的大小会比设计分辨率小一点。
    谈谈为什么要用FairlyGUI以及UGUI的自适应与FairlyGUI的自适应

基于以上的3个种整体适配方式,就可以做我们的ugui的适配。 我们先只好整体适配方式, 然后对于一些比较普通的UI元素, 是不需要关心适配的详细情况的, 对于需要匹配全屏的界面我们只需要设置为随着屏幕扩展就可以了。然后需要处理的是一些特殊情况只有, 需要固定位置的UI元素, 比如我有一个聊天界面, 这个聊天界面是永远固定在屏幕坐下角的, 这个时候就需要使用UGUI提供的锚点的方式处理, 将这个UI元素锚在右下角。 (如果对这个不太理解,估计你还需要再研究一段UI制作和写一段时间的UI)
这应该就是UGUI的最简单的适配方式。

下面我们再来看FGUI的适配, FGUI的适配与UGUI的大同小异的。FGUI提供一个组件名为: UI Content Scaler
谈谈为什么要用FairlyGUI以及UGUI的自适应与FairlyGUI的自适应
图中: Scale Mode 表示你的整体适配方式: 提供选项与Unity的组件差不太多,一般我们选择 Scale With Screen Size , 采用缩放屏幕的方式进行整体适配. Design Resolution 为项目的设计分辨率,会以为基准做缩放处理, Screen Math Mode 为缩放基准选择: 1. 以高度或者宽度适配 2. 以宽度适配 3. 以高度适配。 FGUI没有提供相比于Unity那么完善的配置, 但是这些已经足够我们做适配了。 一般情况下我们都选以高度或者宽度适配来进行适配。
同样的,对于普通的UI元素我们不用考虑适配的问题。 我们只需考虑需要规定位置的UI元素和全屏的UI元素。
这个时候请允许我直接引用官网上面的内容,用于描述我们要做的适配的问题:

全屏界面适配

全局缩放后,大部分UI都不需要做任何调整,只有一个例外,就是设计为全屏的界面。在上例中,在设计分辨率下,全屏界面的大小是960x640,我们也是按这个大小设计全屏组件的。全局缩放后,这时逻辑屏幕的大小变成1138x640了,那大小就不一致了。这是我们需要重新调整组件大小使之满屏,即
//设置组件全屏,即大小和逻辑屏幕大小一致。
//组件的内部应该做好关联处理, 以应对大小改变。
aComponent.SetSize(GRoot.inst.width, GRoot.inst.height);
//或者更简洁的方式
aComponnet.MakeFullScreen();
当然,这仅仅是处理全屏界面的一种方式。在有的情况下,例如如果选用“MatchHeight”模式,也就是高度优先的适配方法,这种方法保证了UI界面垂直方向的内容总是铺满,而水平方向就有可能超出屏幕。这种适配方式需要设计师有“安全区域”的设计思维,不能安排内容在超出屏幕的部分。例如,将全屏界面居中,牺牲掉两边的内容:
aComponent.x = (GRoot.inst.width – aComponent.width)/2;
这样,左边缘和右边缘将会被屏幕边缘裁剪掉,这要求设计师在设计时就考虑到这种情况。
自动调整UI布局

全屏组件在适配过程中需要重新设置全屏,那么组件大小就会发生变化,这时需要使用关联系统让组件内的元素自动布局在正确的位置。实例可以参考关联系统。
全屏界面适配

全局缩放后,大部分UI都不需要做任何调整,只有一个例外,就是设计为全屏的界面。在上例中,在设计分辨率下,全屏界面的大小是960x640,我们也是按这个大小设计全屏组件的。全局缩放后,这时逻辑屏幕的大小变成1138x640了,那大小就不一致了。这是我们需要重新调整组件大小使之满屏,即
//设置组件全屏,即大小和逻辑屏幕大小一致。
//组件的内部应该做好关联处理, 以应对大小改变。
aComponent.SetSize(GRoot.inst.width, GRoot.inst.height);
//或者更简洁的方式
aComponnet.MakeFullScreen();
当然,这仅仅是处理全屏界面的一种方式。在有的情况下,例如如果选用“MatchHeight”模式,也就是高度优先的适配方法,这种方法保证了UI界面垂直方向的内容总是铺满,而水平方向就有可能超出屏幕。这种适配方式需要设计师有“安全区域”的设计思维,不能安排内容在超出屏幕的部分。例如,将全屏界面居中,牺牲掉两边的内容:
aComponent.x = (GRoot.inst.width – aComponent.width)/2;
这样,左边缘和右边缘将会被屏幕边缘裁剪掉,这要求设计师在设计时就考虑到这种情况。

自动调整UI布局

全屏组件在适配过程中需要重新设置全屏,那么组件大小就会发生变化,这时需要使用关联系统让组件内的元素自动布局在正确的位置。实例可以参考关联系统。

所以对于FGUI来说, 使用自己设置全局界面 来替代Unity的自扩张的UI锚点设计, 使用关联系统来替代UGUI的锚点定位功能。

但是再我实际的使用中, 我发现官网的方法在使用有一些困扰。 比如我当前的运行分辨率是跟之前一样是2280x1242, 根据官网的方式去设置, 我会把我需要设置的组件的大小设置成为 2280x1242。 这我想象的不太一样, 我需要的应该是类似Unity的Expand模式下的那种, 以设计的分辨率为基准,经过一点扩张或者缩小正好满足铺满整个屏幕的的分辨率。 因为假如我们底图不是九宫格类型的话, 当进行这样设置之后, 如果设计分辨率和运行分辨率相差太大的话,会导致大量内容看不到, 所以我应该计算当前屏幕经过整体缩放之后的分辨率。然后将我们的全屏界面设置为当前分辨率。

function UIMgr.InitSize()
    UIMgr.DesignWidth = 1334
    UIMgr.DesiginHeight = 750
    scale = math.max(UIMgr.DesiginHeight / GRoot.inst.height,   UIMgr.DesignWidth / GRoot.inst.width)
    UIMgr.width  = GRoot.inst.width * scale + 2
    UIMgr.height = GRoot.inst.height * scale + 2
end

大致代码就在上面了, 我们通过这样计算出当前屏幕的分辨率,然后设置给需要表示为全屏的界面,为什么要+2的原因,是因为这里计算出来数值是个浮点数,会不精确,可能会导致界面出现很小黑边。

function BaseView:MaskBgFull()
    self:MaskAsFull("full_bg")
end

function BaseView:MaskAsFull( name )
    local aObject = self.m_goView:GetChild(name)
    if aObject then
        aObject:SetSize(UIMgr.width, UIMgr.height)
        aObject:SetPosition(0,0,0)
    end
end

上面是我们调用的代码, 在基类封装一个方法, 如果有需要设置为全屏幕的UI元素就调用这个方法, 同时约定好如果有需要设置为全屏幕的背景, 那么在制作UI的时候就设置名字为: full_bg。这个方法会在初始化的时候进行调用。

以上就是我理解的UGUI的适配方法 和 FGUI的适配方法。

诸君共勉。