6.3 高效播放CSB动画

在使用CSLoader加载的节点时,可以让其执行一个ActionTimeline类型的Action,通过调用ActionTimeline的方法可以控制动画的播放和暂停等,一般情况下要播放一个CSB节点的动画时,都是先创建CSB文件对应的ActionTimeline,然后让CSB节点执行,最后调用ActionTimeline的play()方法播放动画。实际上这是一种低效的做法,因为CSB节点的ActionTimeline是不会停止的,也就是说我们只需要一个ActionTimeline就够了,而不是每播放一次动画创建一个。那么应该如何获取到CSB节点当前的ActionTimeline呢?ActionTimeline与其他的Action有两点最大的不同,除了ActionTimeline不会停止之外,ActionTimeline在执行的时候,Action的tag就会被设置为节点的tag。

所以正确的做法应该是这样的,先根据CSB节点的tag获取Action,并动态转换成ActionTimeline(在一些旧版本的引擎中,同一个CSB文件创建出来的多个节点对象的ActionTimeline对象是同一个,所以可能出现播放一个ActionTimeline的动画,所有CSB对象都执行了动画,可以升级引擎或使用ActionTimeline的clone方法解决),如果转换成功则使用这个ActionTimeline来播放动画,否则再使用CSB的路径创建一个ActionTimeline,让CSB节点执行这个Action。但如果在运行之后修改了CSB节点的Tag,或者将这个Action停止了,就无法正确播放动画了。

Cocos2d-x 3.10之前的版本是会自动执行ActionTimeline的,但由于在某些情况下会存在严重的内存泄漏,所以Cocos2d-x 3.10的代码中取消了根节点自动播放ActionTimeline的功能,但嵌套的CSB节点还是会自动播放ActionTimeline。CSLoader的内存泄漏很隐蔽,但危害很大,重现这个内存泄漏的BUG很简单,只需要在一个for循环中不断调用CSLoader创建节点,然后再直接调用release将创建的节点释放,就会产生内存泄漏了,如果加载的是比较复杂的CSB节点,更容易重现这个问题。查看程序占用的内存会发现,程序占用了很大的一块内存。

这个内存泄漏的原因是因为CSB节点在创建的时候自动执行的ActionTimeline,这时候ActionManager会对CSB节点有一个retain操作,增加了它的引用计数,而直接通过autorelease释放CSB节点,但并不会真正释放这个CSB节点,因为没有一个地方让ActionManager执行release的操作,如果在释放之前先执行一下CSB节点的cleanup()方法,就可以解决内存泄漏。