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。

  1. ActionManagerEx是一个单例类,管理所有的动画,一个UI可能有多个动画,存储的结构是一个Map,Map的元素是(UI的文件名称,这个UI对应的所有动画列表)。
  2. ActionObject是一个动画,因为一个动画可能涉及到多个Node,所以存有一个Node的列表。
  3. 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-&gt;_node = node;
        ret-&gt;autorelease();
        return RepeatForever::create(ret);
    }
    else
    {
        CC_SAFE_DELETE(ret);
        return nullptr;
    }
}

ActionWrap::~ActionWrap()
{
    ActionManagerEx::getInstance()-&gt;releaseAction(_node);
}

我们在ActionManagerEx::initWithDictionary读取动画时把动画加载到跟节点的生命周期里面:

dynamic_cast&lt;Node*&gt;(root)-&gt;runAction(ActionWrap::createWrap(root));
    _actionDic.insert(std::pair&lt;Ref*, cocos2d::Vector&lt;ActionObject*&gt;&gt;(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-&gt;getActionNode()-&gt;getParent())
            {
                actionFrame-&gt;setPosition(Point(positionX, positionY)+this-&gt;getActionNode()-&gt;getParent()-&gt;getAnchorPointInPoints());
            }
            else
            {
                actionFrame-&gt;setPosition(Point(positionX, positionY));
            }

小技巧,让cocos2d-x支持多语言Plist图片

 - by Hector

小技巧,让cocos2d-x支持多语言Plist图片

之前看到有人在cocoachina的论坛问这个问题,正巧处理过这个问题,记录一下。

cocos2d-x加载plist图片之后,将纹理放在TextureCache里面,SpriteFrameCache存储了图片名称和纹理的对应关系。

跟踪代码不难发现,所有动画,UI最后或者纹理都是通过图片plist里面的名称来查找纹理的,最终调用的是这个方法:

SpriteFrame* SpriteFrameCache::getSpriteFrameByName(const std::string& name) 其中name,就是plist里面的key。

那么实现方式很简单了,我们在图片后面加上语言后缀,比如-zh.png,-en.png,在SpriteFrameCache添加一个std::vector<std::string> _fileExts;数组,然后修改上面那个函数即可

SpriteFrame* SpriteFrameCache::getSpriteFrameByName(const std::string&amp; name)
{
    SpriteFrame* frame = nullptr;
    for (string&amp; ext:_fileExts) {
        string nametemp = name;
        int pos = nametemp.rfind(".");
        nametemp.insert(pos,ext);
        frame = _spriteFrames.at(nametemp);
        if (frame) {
            return frame;
        }
    }
    if (!frame)
        frame = _spriteFrames.at(name);
…………………………
}

因为纹理已经在内存里面了,切换语言后,清空原来的多语言后缀,新加现语言的后缀即可。逻辑和代码都十分简单,就不再仔细描述了。。。

Cocos2d-x 添加TTF字体

 - by Hector

Cocos2d-x 添加TTF字体

Android

  1. Android如果字体名字存在中文编译有问题,将字体文件名改为英文。
  2. cocos2d-x在Android上字体后缀必须为ttf。

    cocos在android上根据后缀名是否为ttf,来加载字体文件,而且没有判断otf的情况(截至3.0),但Android是支持otf的,所以如果是otf字体,将后缀改为ttf。

  3. 使用时字体文件名用字体的资源路径即可。

iOS

  1. 将字体加载到资源目录,并在info.plistFonts provided by application里面添加字体的资源路径
  2. 使用时要用字体的Family Name,有个GlyphDesigner软件打开字体,可以看到字体的Family Name。

综上,程序里面需要判断一下部署的平台,比如我用了一个mac自带的雅痞otf字体,名字改为yapi.ttf,查得Family Name 为 Yuppy SC,代码如下。

const char* getFontName()
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    return "Yuppy SC";
#else
    return "fonts/yapi.ttf";
#endif
}

cocos2d 3.x中,引擎坐标处理方式不一致,被坑吐血了

 - by Hector

cocos2d 3.x中,引擎坐标处理方式不一致,被坑吐血了

ProtectedNode,Node的锚点都是(0,0)其余的sprite,widget,layer都是(0.5,0.5)。

为了简化描述,下面所有的控件锚点都是默认的(0.5,0.5)

在 beta2之前:

  1. widget添加子widget都是在中心,这点跟cocostudio是一致的。
  2. 其他sprite,layer添加子layer,sprite都是在左下角。

这两个不同我忍了。

在3.0正式版:

  1. widget添加子widget跟sprite,layer一样了,都是左下角。(升级都要改坐标,我忍了)
  2. cocostudio ui解析会错位,后面出了下面这个帖子:

    【通知】Cocos2dx 3.0正式版本UI解析错乱解决方案

    注意改动的代码,setPosition的时候将原来本来坐标是(0,0)的坐标硬生生加上了父控件的锚点偏移,这样达到了和UI编辑器看到的是一致的。

    你也许会想,我明明在编辑器里面设置的是(0,0)的坐标,怎么变了呢? 我说:呵呵

在cocos2d-js alpha2 :

widget添加子widget,在浏览器里面是中心,在手机里面又是左下角~~~~

一处编写,到处运行,你得加上下面的代码吧,我说,呵呵~~~

if(cc.sys.isNative) { child.setPosition(father.getSize().width / 2, father.getSize().height / 2); }