略解cocos2d-x 延迟动画系统。
2016-09-01 11:19
190 查看
最近要做一个轮盘的缓动功能,套用了各种cocos2d-x的缓动公式,均觉不满意。索性操刀子自己特化实现一个。在实现之前,当然要从源码那里偷点东西。于是抽空看了下cocos2d-x的ActionInterval实现。放在这里给懒得看源码的童鞋做个快速浏览。
让我们先来看下正常的动画是怎么调用的。
auto pAction = RotateBy::create(8, 720) ;//EaseSineInOut //EaseExponentialInOut
cbd_->runAction( pAction );//
如上,是一个简单的RotateBy动画。cbd_是一个Sprite。
先来看create发生了什么事
这个函数没什么稀奇,关键在于initWithDuration
而ActionInterval的initWithDuration 则只是简单的设置了一个标志位。 _firstTick = true; 同时将_elapsed = 0;这个_firstTick也是有点画蛇添足。表问我为什么知道。看源码,你也会知道。
那么我们简单认为,ActionInterval::initWithDuration基本只是初始化了duration 及重置经过时间。
以上,create完事。
接下来是cbd_->runAction( pAction );
在这个runaction里面,将这个action加入 actionmanager,由于是在主线程里面操作,因此虽然update函数虽然无时无刻不在执行遍历action队列执行一些操作,所以也是不用加锁。
回到这个runactio的实现
只是加入到_actionManager。然后返回action对象。
然后为什么动作会执行?因为_actionManager 从app开始就一直在执行update。在update里面取出action,更新动画,并检查是否结束,结束则移出数组。。。等等一系列脏活。
回到我们的rotateBy._actionManager会取出我们的rotateBy 这个action 然后调用它的step函数。回到源码,RoateBy是没有step函数的。这里是调用的ActionInterval的Step,
因为RoateBy从它派生。
ActionInterval的step执行了一个奇妙的操作,然后调用了实际RoateBy的update函数。这个设计让缓动应用成为可能。同时,这也意味着,如果你贸然重写任何从ActionInterval派生的update。你都将可能打破动画的效果。为毛?
我们上面说到一个奇妙的操作,那就是ActionInterval的step将 传入的dt累加 然后传入实际动画函数的是 一个百分比数值。代码如下
看到updateDt了吗?它不是传统意义上的deltaTime。而是一个百分比数值,这个数值告诉所有ActionInterval的类的实现者,我现在要求你播放该动画的某某位置。例如1/3 1/2(一半) 或者结束前的最后一帧!
也就是具体动画的实现者,将会从这个updateDt得到自己要求播放的百分比,而不用真正去关心究竟原动画多少秒,过去了多少秒。。。
由于运用了这种机制,因此应用缓动函数就成为可能。
为毛?我刚才说了,动画接受到的是百分比。随意调整这个百分比的增量,就可以实现缓动,加速,减速效果。当然,这个增量曲线不管怎么变化 都应该是从0~1之间。
所以呢,如果自己实现一个ActionInterval的派生类,在这个派生类里面随意扭曲掉这个百分比,然后再将扭曲后的百分比交给真正执行的动画效果,那么该动画就会老老实实按照你扭曲之后的百分比播放。
你要他慢就慢,要他快就快。要他停就停,随便你。。。(具体可以参照EaseExponentialInOut一类的Ease函数的实现)但是这里还是有个隐藏的缺陷,那就是由于是压缩成了百分比,因此存在精度的问题。也就是说,如果你要求的速度值小于float的精度增量,那么,这个东西就不会响应你的要求。
说人话,举个例子,你要求在100秒内转1000000万个圈,并且最后一个圈的速率我要求很慢,慢到什么程度?1秒一个圈。那么1/10000000万 这个精度就已经超出了float的范围。所以,它将不会响应你的要求。它会在第9秒就完全停止转动。
如果你第9秒要求是速度慢到2秒一个圈,对不起,精度不够。轮盘将在第八秒停止。所以当你的减速函数要求减速效果到最后非常慢,非常平滑的时候,对不起,这个百分比闯祸了。
但是,似乎,,,好像,没有人有这么变态的要求吧?轮盘加速,极速,减速 只要差不多就行了!NO。实际生产环境中可能不是这样。
那你会说,那我使用物理效果来实现,设定最后阶段的阻力极小,减速就会很平缓。是的。但是这样你的老板不会满意的。为嘛?你们都懂。
我们可以看到,其实cocos2d的源码并没有想象中的难,要满足生产环境中的使用要求,不仅需要会书本,教程上的东西,而且需要学会自己去深入解析内部的实现。搞清楚它是怎么做的。我要改又应该怎么改。
至于我怎么改的轮盘缓动,由于是一个特化实现,不具备通用价值。因此我就不公布了。毕竟这个东西,仁者见仁,我觉得好,你们未必。
让我们先来看下正常的动画是怎么调用的。
auto pAction = RotateBy::create(8, 720) ;//EaseSineInOut //EaseExponentialInOut
cbd_->runAction( pAction );//
如上,是一个简单的RotateBy动画。cbd_是一个Sprite。
先来看create发生了什么事
RotateBy* RotateBy::create(float duration, float deltaAngle) { RotateBy *rotateBy = new (std::nothrow) RotateBy(); rotateBy->initWithDuration(duration, deltaAngle); rotateBy->autorelease(); return rotateBy; }
这个函数没什么稀奇,关键在于initWithDuration
bool RotateBy::initWithDuration(float duration, float deltaAngle) { if (ActionInterval::initWithDuration(duration)) { _deltaAngle.x = _deltaAngle.y = deltaAngle; return true; } return false; }
而ActionInterval的initWithDuration 则只是简单的设置了一个标志位。 _firstTick = true; 同时将_elapsed = 0;这个_firstTick也是有点画蛇添足。表问我为什么知道。看源码,你也会知道。
那么我们简单认为,ActionInterval::initWithDuration基本只是初始化了duration 及重置经过时间。
以上,create完事。
接下来是cbd_->runAction( pAction );
在这个runaction里面,将这个action加入 actionmanager,由于是在主线程里面操作,因此虽然update函数虽然无时无刻不在执行遍历action队列执行一些操作,所以也是不用加锁。
回到这个runactio的实现
Action * Node::runAction(Action* action) { CCASSERT( action != nullptr, "Argument must be non-nil"); _actionManager->addAction(action, this, !_running); return action; }
只是加入到_actionManager。然后返回action对象。
然后为什么动作会执行?因为_actionManager 从app开始就一直在执行update。在update里面取出action,更新动画,并检查是否结束,结束则移出数组。。。等等一系列脏活。
回到我们的rotateBy._actionManager会取出我们的rotateBy 这个action 然后调用它的step函数。回到源码,RoateBy是没有step函数的。这里是调用的ActionInterval的Step,
因为RoateBy从它派生。
ActionInterval的step执行了一个奇妙的操作,然后调用了实际RoateBy的update函数。这个设计让缓动应用成为可能。同时,这也意味着,如果你贸然重写任何从ActionInterval派生的update。你都将可能打破动画的效果。为毛?
我们上面说到一个奇妙的操作,那就是ActionInterval的step将 传入的dt累加 然后传入实际动画函数的是 一个百分比数值。代码如下
void ActionInterval::step(float dt) { if (_firstTick) { _firstTick = false; _elapsed = 0; } else { _elapsed += dt; } float updateDt = MAX (0, // needed for rewind. elapsed could be negative MIN(1, _elapsed / MAX(_duration, FLT_EPSILON) // division by 0 ) ); if (sendUpdateEventToScript(updateDt, this)) return; this->update(updateDt); }
看到updateDt了吗?它不是传统意义上的deltaTime。而是一个百分比数值,这个数值告诉所有ActionInterval的类的实现者,我现在要求你播放该动画的某某位置。例如1/3 1/2(一半) 或者结束前的最后一帧!
也就是具体动画的实现者,将会从这个updateDt得到自己要求播放的百分比,而不用真正去关心究竟原动画多少秒,过去了多少秒。。。
由于运用了这种机制,因此应用缓动函数就成为可能。
为毛?我刚才说了,动画接受到的是百分比。随意调整这个百分比的增量,就可以实现缓动,加速,减速效果。当然,这个增量曲线不管怎么变化 都应该是从0~1之间。
所以呢,如果自己实现一个ActionInterval的派生类,在这个派生类里面随意扭曲掉这个百分比,然后再将扭曲后的百分比交给真正执行的动画效果,那么该动画就会老老实实按照你扭曲之后的百分比播放。
你要他慢就慢,要他快就快。要他停就停,随便你。。。(具体可以参照EaseExponentialInOut一类的Ease函数的实现)但是这里还是有个隐藏的缺陷,那就是由于是压缩成了百分比,因此存在精度的问题。也就是说,如果你要求的速度值小于float的精度增量,那么,这个东西就不会响应你的要求。
说人话,举个例子,你要求在100秒内转1000000万个圈,并且最后一个圈的速率我要求很慢,慢到什么程度?1秒一个圈。那么1/10000000万 这个精度就已经超出了float的范围。所以,它将不会响应你的要求。它会在第9秒就完全停止转动。
如果你第9秒要求是速度慢到2秒一个圈,对不起,精度不够。轮盘将在第八秒停止。所以当你的减速函数要求减速效果到最后非常慢,非常平滑的时候,对不起,这个百分比闯祸了。
但是,似乎,,,好像,没有人有这么变态的要求吧?轮盘加速,极速,减速 只要差不多就行了!NO。实际生产环境中可能不是这样。
那你会说,那我使用物理效果来实现,设定最后阶段的阻力极小,减速就会很平缓。是的。但是这样你的老板不会满意的。为嘛?你们都懂。
我们可以看到,其实cocos2d的源码并没有想象中的难,要满足生产环境中的使用要求,不仅需要会书本,教程上的东西,而且需要学会自己去深入解析内部的实现。搞清楚它是怎么做的。我要改又应该怎么改。
至于我怎么改的轮盘缓动,由于是一个特化实现,不具备通用价值。因此我就不公布了。毕竟这个东西,仁者见仁,我觉得好,你们未必。
相关文章推荐
- 【iOS-Cocos2d游戏开发之七】在cocos2d中添加/删除系统组件,并解决View设置透明会影响View中的其他组件的问题!更新解决添加组件Cocos2d动画停止播放的BUG】
- Cocos2d-x 系统动画
- 【iOS-Cocos2d游戏开发之七】在cocos2d中添加/删除系统组件,并解决View设置透明会影响View中的其他组件的问题!【11月28日更新解决添加组件Cocos2d动画停止播放的BUG】
- Cocos2d-x 系统动画
- cocos2d-x 系统动画
- 【Cocos2d-x游戏引擎开发笔记(4)】系统动画
- 【Cocos2d-x游戏引擎开发笔记(4)】系统动画
- cocos2d-x3.2中的动画系统
- 【iOS-Cocos2d游戏开发之七】在cocos2d中添加/删除系统组件,并解决View设置透明会影响View中的其他组件的问题!【11月28日更新解决添加组件Cocos2d动画停止播放的BUG】
- cocos2d-x开发记录:二,基本概念(动作,动画,坐标系统)
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十七)战斗前夜之构建动态障碍物系统
- 模型动画系统设计的一些心得
- 基于骨骼动画的生物拼接和人物动作系统设计
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十八) 经典式属性设计及完美的物理攻击系统
- 如何避免打开键盘或横竖屏切换,active重启,处理界面的延迟动画
- 模型动画系统设计的一些心得
- HP 和 Compaq 台式电脑 - 在 Windows Vista 中执行 HP 系统恢复(Flash 动画演示)
- 引擎--渲染器,顶点变换和光照,剔除(culling)系统和LOD系统,阴影贴图,粒子系统,着色器,骨骼动画
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十七)战斗前夜之构建动态障碍物系统
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十八) 经典式属性设计及完美的物理攻击系统