Unity3D之UI设计

来源:互联网 发布:ubuntu 退出vim 编辑:程序博客网 时间:2024/05/17 02:31

UI程序设计

首先先说一下UI程序设计, 差不多是玩家打开一个游戏最先看到的东西, 差不多也是玩家最不在意的东西. 对开发者来说, 几乎每个游戏模块都与 UI 有联系, 处理不当 UI 就是恶梦. 宽高比适应, 分辨率适应, 像素对齐…光是这些就足够没有经验的开发者浪费大量的时间了. 好在 Unity 这样的引擎提供了已经很强大的 UI 解决方案, 以及许多其他开发者提供的插件. 但是这还不够. 这篇文章主要谈一谈 UI 的逻辑设计而不是排版设计上的经验, 同时也在说明我的插件可以用来解决什么问题.


一、使用事件
屏幕角落里有一个 “血条”, 代表玩家的生命值. 决战的时候到了, 玩家抽出他的刚剑与怪物厮杀. 几乎没有人可以毫发无伤的打败怪物. 那么问题来了, 应该在什么时候修改 “血条” 的填充率?

一个偷懒的做法是每帧去读取玩家的生命值, 然后应用到 “血条” 的填充率上. 好吧这的确可以, 暂时没什么问题 (其实还有一
种人神共粪的办法是依据 “谁开发谁保护, 谁污染谁治理” 原则…). 然而策划说, 当玩家被攻击时,  “血条” 应当播放一个粒子动画效果, 因为帅. 如果你还是不打算改进方法, 那就缓存玩家的生命值然后每帧对比吧! 但是接下来策划又说, 因中毒损失生命值时播放的粒子效果是紫色的, 因被攻击损失生命值时播放的粒子效果是黄色的. 啊哦, 你放弃了, 你不想改了, 都写那么多了不是吗? 你严肃的对策划说, 这个做不了, 要大改, 游戏做不完的话, 这锅……

观察者模式闪亮登场! 如果你与世无争对<四十二章经>这种神功秘笈没有兴趣的话, 那么应当很了解 C# 的 delegate 和 event 了 (不了解的点这里). 如果玩家每次生命值变化都大叫一声 “我因为什么原因而损伤/增加多少生命值”, AI 模块可以听见, UI 模块可以听见, Story 模块可以听见, 整个世界都可以听见……我的意思是, UI 在初始化时将 OnPlayerHpChange 方法注册到玩家的 onHpChange 事件里, 当事件触发时根据具体的参数来修改 “血条” 填充率和播放相应的粒子效果, 多么简洁优雅!
可是不要忘了在恰当的时候取消注册事件. 这大概是最常发生的错误了. HUD 是作为一个非游戏核心模块存在的, 一般仅用来展示
一些玩家需要关心的信息. 无论如何游戏不能因为 HUD 模块的错误而停止运行. 然而忘记取消注册事件可能导致这种情况发生. 比如某些 HUD 对象已经销毁后, 玩家生命值发生变化时触发这些 HUD 对象曾经注册的事件就会造成空引用异常. 另外一种不取消注册事件造成的错误是, 当游戏关卡重新开始时, HUD 因重新初始化而重复注册事件, 相同的方法在事件触发后执行多次.

 
二、使用动画
制作精美的游戏, 几乎都少不了带动画的 UI. 生硬的直接弹出窗口的方式已经是过去式了. 好吧, 那么加动画就是了, 有什么可以说的呢?比如说活动窗口切换问题. 因为动画的存在, 活动窗口的变化不再是 “瞬间完成” 的而是 “经历一段时间”. 时间就是问题. 现在我们在游戏主菜单界面, 点了一下 Options 按钮. 主菜单界面开始滑出屏幕左边界, Options 界面从屏幕右边界滑入. 在动画过程中, 如果玩家手速惊人, 又点了一下主菜单界面的开始游戏按钮会发生什么事呢? 或者只是快速的连续点击了 Options 按钮呢? 
我们可以认为没有人想这么做, 除非他是误操作的. 好吧既然是误操作那么就忽略吧, 离开一个界面的动画开始时就立即关闭这个
界面的 “可交互性”. 那么什么时候打开新界面的 “可交互性” 呢? 其实在动画开始到结束的任何时候都可以, 但是从避免误操作的角度出发还是应当在动画结束后才打开 “可交互性”.
然后是动画控制问题. 现在我们有两个界面同时发生移动, 可能还有颜色, 不透明度, 缩放等一系列视觉属性的变化, 甚至还有 3D 相机的位置和朝向的变化. 我们希望所有的动画同时开始, 同时结束, 并且大多数动画具备相同的变化过程, 比如先加速后减速. 既然如此, 为何不设计一种动画控制器来负责这一切呢? 可以调整变化曲线, 可以设置持续时间, 可以关联多个动画元素, 可以触发结束事件……当动画开始的时候, 我们只需要打开这一个控制器, 这个控制器负责更新所有关联的动画, 最后在动画结束时打开新界面的 “可交互性”.

最后是开发成本问题. 为了兼容各种不同的设备, 开发中 UI 本身就十分复杂易变, 再加上 UI 动画的存在让制作和修改成本再上
一层楼. 在动画这一方面, 如果可以避免技术人员重复劳动, 修改代码, 甚至不需要技术人员的参与就可以实现, 无疑可以省去大
量的成本. 在使用 Unity 这样的完善的开发工具时, 在编辑器中就可以方便的编辑和预览动画将可以大大减少技术人员的开发时间.

三、使用栈状态机
你可能知道栈, 也可能知道状态机, 但栈状态机是什么鬼?
你的主菜单界面有 5 个按钮, 每个按钮点击后都打开一个新的界面, 同时让主菜单界面消失. Easy! 聪明的你写了 “隐藏主菜单
” 和 5 个 “显示xx界面” 方法在每个按钮事件里调用. 这没什么嘛. 然后打开的新界面都有返回主菜单的按钮, 你同样写了 5 个 “隐藏xx界面” 和 “显示主菜单” 的方法, 在返回按钮事件里调用它们. 你开始有点烦了. 更麻烦的是每个打开的界面还可以打开下一级界面, 每个按钮都要绑定一个 “隐藏当前界面” “显示下一个界面” 的方法, 最后你会想, 为什么我总是重复做一件相同的事情?
因为你没有使用状态机. 主菜单界面的 5 个按钮, 虽然点击后目标界面不同, 但都需要先离开主菜单. 如果每个界面是一个 “状
态”, 当进入状态时显示这个界面, 离开状态时隐藏这个界面, 那么只要状态发生变化, 离开和进入的状态都会做好自己的事情, 
这样每个按钮的唯一任务就是切换状态了.
我们再进一步思考这个问题. 既然同一时间只有一个活动界面, 并且只能从这个界面进入下一层界面或返回上一层界面, 这不正是 “栈” 的概念吗? 如果所有的状态都存储在栈里, 那么 “进入下一层界面” 就是栈的 Push 操作, “返回上一层界面” 就是栈的 Pop 操作. 哇哦, 听起来很不错的样子……但仔细想想, 与使用普通的状态机相比, 栈状态机 Push 操作与普通状态机 
SetState 一样需要一个新状态参数, 只是 Pop 操作比普通状态机 SetState 省略了一个参数而已. 似乎并没有什么根本的区别嘛! 
这里我们再扩展 “状态” 为 “栈状态”, 在 OnEnter 和 OnExit 基础上添加 OnPush 和 OnPop, 就可以做更多的事情了! 想一
想, 一个新界面入栈的时候, 旧界面不必完全消失, 可以只是不可交互了而已. 这样就可以实现更丰富的 UI 效果了!

现在你已经掌握了开发 UI 的秘诀了, 而且也不必重新造轮子,  White Cat’s Toolbox插件可以省去你半年的休息时间, 更重要的是可以立即为你创造更多价值. 而且这已经是第三个版本了. 当然不仅可以做 UI, 还有许多其他实用的工具, 比如路径. 几乎所有的东西都可以通过脚本访问和扩展. 包含完整源代码哦!

UI优化

UGUI的优点

1、所见即所得的编辑方式,在Scene窗口中即可编辑。 
2、智能的Sprite packer可以将图片按tag自动生成图集而无需人工维护,生成的图集合并方式比较合理,无冗余资源。 
3、渲染顺序与GameObject的Hierarchy顺序相关,靠近根节点显示在底层,而靠近叶子节点显示在顶层;这样的渲染方式使得调整UI的层级比较方便和直观。 
4、RectTranForm及锚点系统更适合于2D平面布局,并且非常方便多分辨率屏幕自适配。

关于UGUI优化的,或许你会觉得UI的制作规范及指导方法与优化无关,其实很多性能问题往往是资源的不合理使用造成的,比如使用了尺寸过大的图片、引用了过多的图集以及加载了不必要的资源等。如果从设计和制作UI一开始就遵守特定的规范,则可以规避不必要的性能开销。笔者根据参与的多个项目总结了以下几点通用的规范和指导方法(这些规范适用于所有项目,不管你使用UGUI还是NGUI)。 

优化其一: 合理的分配图集 
合理的分配图集可以降低drawcall和资源加载速度;具体细节如下: 
1、 同一个UI界面的图片尽可能放到一个图集中,这样可以尽可能的降低drawcall。 
  
2、 共用的图片放到一个或几共享的图集中,例如通用的弹框和按钮等;相同功能的图片放到一个图集中, 例如装备图标和英雄头像等;这样可以降低切换界面的加载速度。 
  
3、 不同格式的图片分别放到不同的图集中,例如透明(带Alpha)和不透明(不带Alpha)的图片,这样可以减少图片的存储空间和占用内存。(UGUI的sprite packer会自动处理这种情况) 
  
优化其二、 resources目录中应该只保存prefab文件,其它非prefab文件(例如动画,贴图,材质等)应放到resource目录之外 
因为随着项目的迭代,可能会导致部分资源(动画,贴图)等失效,如果这些文件放在resource目录下,在打包时,unity会将resource目录下文本全部打成一个大的AssetBundle包(非resouce目录下的文件只有在引用到时才会被打到包里),从而出现冗余,增加不必要的存储空间和内存占用。
  
优化其三、 关卡内的UI资源不要与外围系统UI资源混用 
在关卡内,需要加载大量的角色及场景资源,内存比较吃紧,一般在进入关卡时,都会手动释放外围系统的资源,以便使关卡内有更多的内存可以使用。如果战斗内的UI与外围系统的UI使用相同图集里的图片,则有可能会使得外围系统的图片资源释放不成功。对于关卡内与外围共用的UI资源需要特殊处理,一般来说复制一份出来专门给关卡内使用是比较好的选择。 
  
优化其四、适当的降低图片的尺寸 
有时UI系统的背景可能会使用全屏大小的图片,比如在Iphone上使用1136*640大小的图片;使用这样尺寸的图片代价是很昂贵的,可以和美术同学商量适当的降低图片的精度,使用更低尺寸的图片。 
  
优化其五、 在android设备上使用etc格式的图片 
目前,几乎所有android设备都支持etc1格式的图片,etc1的好处是第个像素点只战用0.5个字节而普通rgba32的图片每个像素点占4个字节,也就说一张1024*1024图片如果使用rgba32的格式所占用的内存为4M而etc1格式所占用的内存仅为0.5M。但是使用etc1格式的图片有两个限制——长和宽必须是POT的(2的N次方)并且不支持alpha通道,因此使用etc1时需要额外的一张图来存储alpha通道,并且使用特殊的shader来对alpha采样。
  
优化其六、 删除不必要的UI节点、动画组件及资源 
随着项目的迭代,可能有部分ui节点及动画已经失效,对于失效的节点及动画一定要删除,在很多项目中,有部分同学为了方便省事,只是将失效的节点及动画disable了。这样做虽然在运行时不会对cpu造成太多负担,但是在加载时会增加不必要的加载时间以及内存占用。对于废弃的UI图片资源,虽然未放到Resource目录最终不会打到包里,但是在Editor模式下仍然会打到图集中从而影响优化决策。