闲谈设计模式——基于UI动画框架

15 篇文章 0 订阅
9 篇文章 0 订阅

  CEGUI是一个C++实现的面向对象开源GUI库,使用CEGUI开发界面的大型RPG游戏已有不少。几年来,我参与过3个MMORPG项目,都是用CEGUI开发游戏界面。在产品的不断迭代过程中,“方便快捷地实现UI动画”一直都是一种强烈的功能需求,比如:

  • 当玩家经验累积足够之后,需要一个跳动的箭头指向技能按钮,以提示玩家可以升级技能
  • 当农场的菜成熟之后,需要一个特效在菜品上面闪烁,以提示玩家可以收获
  • 当从地上捡起物品时,需要一个物品图标从起点以抛物线轨迹飞到包裹,闪烁一下,然后消失
  • 当从自己的农场进入到别人的农场时,自己的农场淡出,别人的农场淡入
  • 当点击某个地方时,由小到大弹出一个窗口,关闭时,窗口由大到小消失
  • 当鼠标离开一个小窗口2秒后,窗口由大到小消失
  • 当收到好友的一个特殊的消息时,聊天框上下左右震动几下
  • 使用一个物品后,物品图标向上飘起,并渐隐消失
  • 当给坐骑喂食时,食物从包裹以指定轨迹(比如抛物线)飞向坐骑面板,然后消失

  类似的需求不一而足。我参与的几款RPG游戏中,用到UI动画的地方不下500处!但整个产品部门都没有一种统一的有效的解决方案。在需要UI动画的时候,开发人员都是就地硬编码动画逻辑,比如“关闭一个窗口时,需要窗口慢慢向上飘起,慢慢缩小,并慢慢消失”,实现可能是这样的:

function XXModule:OnCloseWindow()
    -- 业务逻辑代码
    ...
    -- 窗口关闭动画
    self.lastTick = GetTickCount() -- 记录上一次的时间
    SetTimer(50, 10, self.Animation_OnTimer, self) -- 50ms调用1次,共调用10次
end

function XXModule:Animation_OnTimer()
    locla win = GuiWinMgr:getWindow(self.WindowName) -- 取需要播放动画的窗口对象

    local curTick = GetTickCount() -- 当前tick
    local y = CalcYPos(curTick - self.lastTick) -- 根据时间计算y坐标的新值
    local postion = string.format("{{0,0},{0,%g}}", y)
    win:setProperty("UnifiedPosition", postion)
    
    local alpha = CalcAlpha(curTick - self.lastTick) -- 根据时间计算alpha变化值
    win:setAlpha(alpha) -- 设置窗口透明度

    local size = CalcSize(curTick - self.lastTick) -- 根据时间计算窗口尺寸变化的大小
    win:setProperty("UnifiedAreaRect", size)
end

-- 根据时间计算位置
function XXModule:CalcYPos(ticks)
    ....
end

-- 根据时间计算alpha变化值
function XXModule:CalcAlpha(ticks)
    ....
end

-- 根据时间计算窗口尺寸变化的大小
function CXXModule:alcSize(ticks)
    ....
end

  你看到了,“我”在业务逻辑中,精准地计算时间、位置、alpha以及size!虽说,将动画逻辑封装到了几个函数里面,但至少控制逻辑(比如定时器)仍在业务代码中。不同的产品中遇到同样的动画需求,每个人也都是各自实现了五花八门的轮子。强迫症的我感觉到了若干不爽(包含但不限于):

  ✘  硬编码,极其僵化,无法动态调整动画效果
  ✘  需要在业务代码中为动画逻辑增加控制字段,比如定时器变量等,而这些跟业务逻辑无关
  ✘  动画逻辑和业务逻辑紧密耦合,影响业务逻辑的清晰性,降低业务代码的可维护性
  ✘  如果动画不再需要了,要小心翼翼的从业务代码中将其剥离
  ✘  动画逻辑不能复用,每个需要动画的地方只能重新发明轮子
  ✘  动画逻辑的代码需要操纵UI层对象,学习和理解成本较高

  作为一个业务逻辑的实现者,我想做的应该是这样的:

function XXModule:OnCloseWindow()
    -- 业务逻辑代码
    ...
    -- 让窗口执行一段动画(这段动画的配置id是123)
    local win = GuiWinMgr:getWindow(self.WindowName)
    GuiWinMgr:RunAnimation(win, 123)
end

  就这样!我不想了解UI对象的各种属性,不想了解更多和业务无关的东西了!在这里,我只想说,让这个窗口“慢慢向上飘起,慢慢缩小,并慢慢消失”吧!除了GuiWinMgr:RunAnimation(win, 123)这句,我不想再多写一行代码。我不关心动画效果具体怎么实现!

  鉴于此,本人在CEGUI源码中手撸了一个UI动画框架,基于这个框架,开发者按照指定的规则在配置文件中配置好动画策略,然后在业务代码中就可以像上面那样,干净利落地实现想要的动画效果了。有关动画的配置看起来像是这样的(以xml格式配置):

<strategys id = "123" desc = "窗口慢慢向上飘起,慢慢缩小,并慢慢消失" >
    <strategy id = "frame" param = "30" /> <!-- 30ms一帧 -->
    <strategy id = "move" param = "vertical;10;-20" /> <!-- 从起始位置向上移动10次,每次20像素 -->
    <strategy id = "scale" param = "1;0.8;0.8;6" /> <!-- 尺寸缩小10次,每次长宽均缩小0.8 -->
    <strategy id = "fade" param = "1;0.8" /> <!-- alpha从1.0起每次淡化0.8 -->
</strategys>

  本博将开一个系列来讨论这个UI动画框架,这个框架用了不少设计模式的思想,因此顺便也聊聊设计模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值