CocoStudio UI动画的Bug修复与重构
- by Hector
可能是开发组看到这篇文章,跟其相关人员沟通后,其反应开发组已经着手重构UI动画。
— end of 2014.6.10
针对与3.0x,这里的改造没有pull request到官方,主要是我的修改是限制于现在的UI动画架构之下,而且改动较大,官方估计不会merge。希望官方能够完善这块,将UI动画纳入到Node动画中统一管理,UI动画的生命周期能够控制起来。
所有修改在这里(请忽略submodule):https://github.com/myourys/cocos2d-x/commit/a2d85cc353f0eaf96c93d736c55c647802d8e66e
对于cocostudio UI动画,我觉得是一个非常好的东西,可以很方便的在UI编辑器中实现一些针对与UI控件的动画,比如你的界面中有个小人的头一直在那摇晃,如果在动画编辑器中实现,或者手工实现再加入UI中,显然比较麻烦。
官方实现解析
或许是每找到正确使用UI动画的方法,个人觉得官方的实现只是一个初稿,或者说是一个残次品,基本不能用。关于UI动画实现有三个类ActionManagerEx,ActionObject,ActionNode。
- ActionManagerEx是一个单例类,管理所有的动画,一个UI可能有多个动画,存储的结构是一个Map,Map的元素是(UI的文件名称,这个UI对应的所有动画列表)。
- ActionObject是一个动画,因为一个动画可能涉及到多个Node,所以存有一个Node的列表。
- ActionNode是对应UI中的实际控件,一个ActionNode包含一个Object(对应界面中的Node).
在读取UI界面完成后,会继续读取动画,已经动画(ActionObject)对应的动作节点(ActionNode),供程序调用播放。
重构
1.UI动画存储重构
因为UI动画是(UI的文件名称,这个UI对应的所有动画列表)存在map中,这就使得我们根本没办法加载两次UI,或者对动画所有改动后会影响再次加载(更何况官方里面根本没有删除一个单独UI动画的方法),一个UI的所有动画应该属于它自己的,所以我改为(UI根节点,UI对应的所有动画)
。 std::unordered_map<Ref*, cocos2d::Vector<ActionObject*>> _actionDic;
相关存取动画的函数修正不一一赘述。
2.UI动画生命周期
官方对完全没有管理UI动画应该什么时候释放等相关问题,当UI释放时,其动画还在播放,就会报错。 我觉得UI动画生命周期应该跟随其根节点,但是显然UI动画不像Node里面的Action一样是亲儿子用ActionManager统一管理。考虑到UI动画的独立性,我们不在Node的动画体系里面改,而是用一个技巧将UI动画加到Node动画体系里面。
我们用创建一个RepeatForver,然后什么都不做加到根节点的动画里面,根节点在stop、clean其动画时,会release这个RepeatForver,我们在其析构函数里面清除这个UI的所有动画。核心代码:
Action* ActionWrap::createWrap(cocos2d::Ref* node) { ActionWrap *ret = new ActionWrap(); if (ret ) { ret->_node = node; ret->autorelease(); return RepeatForever::create(ret); } else { CC_SAFE_DELETE(ret); return nullptr; } } ActionWrap::~ActionWrap() { ActionManagerEx::getInstance()->releaseAction(_node); }
我们在ActionManagerEx::initWithDictionary读取动画时把动画加载到跟节点的生命周期里面:
dynamic_cast<Node*>(root)->runAction(ActionWrap::createWrap(root)); _actionDic.insert(std::pair<Ref*, cocos2d::Vector<ActionObject*>>(root, actionList));
还有,我们在清除UI动画之前一定要stop掉动画,因为引用计数的原因,动画要在下一帧才会释放,这时动画可能还在播放,就会报错(官方的清除动画时也没有先停止动画,故经常会出现报错的地方)。
3.播放重构
细心的你会发现,播放ui循环动画时一直会打印Log removeAction: Target not found
,因为Node动画机理,动画播放完毕之后自然会清除,UI动画循环播放之前会先stop一次,这时动画已经不存在了,自然会打印上面的Log.
官方是通过schedule更新动画的,另外你读ActionObject::play()
和void ActionObject::simulationActionUpdate(float dt)
会发现,这里逻辑很有问题,循环播放会反复schedule和unschedule定时器。循环动画播放的逻辑更是一团糟。
另外在ActionObject::stop时,也检查node动作是否已经完成,如果已完成ActionNode则不需要stop。
官方原来并没有对_bPlaying变量实现,加了对应的处理。
具体的改动请对比改动的代码。
4.回调函数的处理
因为官方没有管动画的生命周期,然后其回调函数也根本没管,加入动画的回调函数没有retain,想要实现动画的回调必须自己想办法去管理这个回调的释放。由于我们上面管理了动画的生命周期,我们自然也可以把回调函数也加进去(也就是加入是retain以下,释放动画的时候release掉)。
5.动画位置错误Bug
在正式版本中有一个Bug,设置位置时需要加上父节点的偏移位置,官方解决办法:Cocos2dx 3.0正式版本UI解析错乱解决方案,但是这个是没有解决动画位置错乱的,因此有如下改动:
ActionNode::initWithDictionary中 initActionNodeFromRoot(root);
从最后一行拿到代码第二行 actionFrame->setPosition(Point(positionX, positionY));
改为
if(this->getActionNode()->getParent()) { actionFrame->setPosition(Point(positionX, positionY)+this->getActionNode()->getParent()->getAnchorPointInPoints()); } else { actionFrame->setPosition(Point(positionX, positionY)); }