Cocos2d-x动画
声明:本文分析的是cocos2d-x-3.12的代码
游戏中动画可以分为两类,帧动画和骨骼动画。帧动画由一张一张的完整的动画图片组成,骨骼动画由一张张分散的局部动画“拼凑”而成,动作由一个配置文件控制。cocos2d支持这两种动画。
Cocos将帧动画设置成一个延时动作(IntervalAction),精灵Sprite只需要运行一个动作就可以显示帧动画。帧动画的类图大致如下:
帧动画封装再Animation类中,Animation类中有一个数组Vector<AnimationFrame*> _frames
,存放了帧动画的每一帧。AnimationFrame类是帧动画的一帧,每一帧的纹理保存在SpriteFrame *_spriteFrame变量中,另外还有一个变量float AnimationFrame::_delayUnits,这个变量保存是该帧需要停留的多少个时间单位,不是具体的时间,时间单位由Animation类的float _delayPerUnit变量控制。一个帧具体的停留时间为delayUnits*delayPerUnit。
这样设计有一个好处,可以很方便的控制动画的播放速度,只需要更改Animation::_delayPerUnit就可以。
要让一个帧动画显示出来,需要先创建一个Animate延时动作,然后让Sprite类执行Animate动作。
AnimationCache类可以缓存已经加载过的动画,后面再做详细讨论。
帧动画的创建
创建一个帧动画的过程大致如下,
代码如下:
//加载帧动画资源
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("friend-zombie.plist");
//创建这动画的所有帧
VectorSpFrs;
chartemp[50]= { 0 };
for(inti= 1; i < 64; ++i)
{
snprintf(temp, sizeof(temp)/ sizeof(char),"friend-zombie-01-%04d.png", i);
SpFrs.pushBack(AnimationFrame::create(SpriteFrameCache::getInstance()->getSpriteFrameByName(temp),1, ValueMap()));
}
//创建帧动画
autoanimation= Animation::create(SpFrs,0.1, 1000);
//创建帧动画动作
autoanimate= Animate::create(animation);
autosp2= Sprite::create();
sp2->setPosition(150, 200);
//运行动画
sp2->runAction(animate);
Animation创建函数
static Animation*create(constVector<AnimationFrame*>&arrayOfAnimationFrameNames,floatdelayPerUnit,unsigned intloops= 1);
static Animation*createWithSpriteFrames(constVector<SpriteFrame*>&arrayOfSpriteFrameNames,floatdelay= 0.0f,unsigned intloops= 1);
创建函数可以通过一个AnimationFrame数组或SpriteFrame数组创建,使用SpriteFrame数组会将每一帧停留的时间单位设置为1。
参数delay,代表每个时间单位的具体时间,单位为秒。
参数loops,动画循环的次数。
帧事件
在播放帧动画时,有时需要在播放到某一帧或者结束时执行一些操作,Cocos绘制帧时可以发送一个EventCustom自定义事件,事件名用以下宏定义
#define AnimationFrameDisplayedNotification "CCAnimationFrameDisplayedNotification"
事件是否发送取决于帧对应的AnimationFrame类中的ValueMap _userInfo变量,如果该Map变量不为空这会将该变量作为自定义事件的额外参数发送自定义事件。例如:
AnimationFrame*f= AnimationFrame::create(spriteframe,1,ValueMap());
f->getUserInfo().insert(std::make_pair("Finish",Value("finish")));
...//用 f 创建一个Animation
auto l= EventListenerCustom::create(AnimationFrameDisplayedNotification,[](EventCustom* e){
constValueMap*vm= static_cast(e->getUserData())->userInfo;
log("%s, %s",vm->begin()->first.c_str(), vm->begin()->second.asString().c_str());
});
_eventDispatcher->addEventListenerWithFixedPriority(l,1);
AnimationCache
AnimationCache是一个动画缓存类,该类的Map<std::string,Animation*>_animations变量存放了已加载的动画,Key存放的是动画的名字,这个名字可以任意。
可以通过以下函数将一个已创建的Animation添加到缓存中
void AnimationCache::addAnimation(Animation *animation,const std::string &name)
AnimationCache支持读取配置文件添加动画,这样可以简化帧动画的创建。Animation支持两种配置文件格式,都是XML文件进行配置,一种是简单模式,一种相对复杂。
简单模式的配置
animations
walkFront
delay
0.2
frames
friend-zombie-01-0000.png
friend-zombie-01-0001.png
friend-zombie-01-0002.png
friend-zombie-01-0003.png
friend-zombie-01-0004.png
friend-zombie-01-0005.png
friend-zombie-01-0006.png
friend-zombie-01-0007.png
这种模式和Animation::initWithSpriteFrames函数类似,只是指定了一组SpriteFrame名字,这个名字时存储在SpriteFrameCache中的名字,所以添加动画前需要先用SpriteFrameCache加载好所有的帧,创建的帧动画时每帧都用一个时间单位,循环次数也为1。例:
//必须先加载好图片资源
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("friend-zombie.plist");
AnimationCache::getInstance()->addAnimationsWithFile("animations.plist");
autosp1= Sprite::create();
sp1->setPosition(150, 200);
autoanimation= AnimationCache::getInstance()->getAnimation("walkFront");
sp1->runAction(Animate::create(animation));
复杂配置格式
animations
WalkRight
delayPerUnit
0.2
restoreOriginalFrame
loops
200
frames
spriteframe
friend-zombie-01-0008.png
delayUnits
1
notification
firstframe
spriteframe
friend-zombie-01-0009.png
delayUnits
1
spriteframe
friend-zombie-01-0010.png
delayUnits
1
spriteframe
friend-zombie-01-0011.png
delayUnits
1
spriteframe
friend-zombie-01-0012.png
delayUnits
1
spriteframe
friend-zombie-01-0013.png
delayUnits
1
spriteframe
friend-zombie-01-0014.png
delayUnits
1
spriteframe
friend-zombie-01-0015.png
delayUnits
1
properties
spritesheets
friend-zombie.plist
format
2
这种配置格式可以配置每一帧的参数类似于Animation::initWithAnimationFrames函数,可以配置循环次数,每一帧的单位时间,也可以通过
AnimationCache::getInstance()->addAnimationsWithFile("animations2.plist");
autosp1= Sprite::create();
sp1->setPosition(150, 200);
autoanimation= AnimationCache::getInstance()->getAnimation("WalkRight");
sp1->runAction(Animate::create(animation));
Cocos支持Spine的骨骼动画,Spine骨骼动画由各个小图片组成的大纹理,一个纹理配置文件,一个动画配置文件组成。
纹理是由各个动画里的组件构成,如手、脚等等
纹理配置记录了每个组件在大纹理里面的位置。
动画配置文件,该文件为一个Jason文件,记录了各个时刻个组件的位置
Cocos中Spine骨骼动画有一个单独的工程libSpine,这个工程由对骨骼动画的实现。要使用骨骼动画必须包含头文件#include"spine/spine-cocos2dx.h",骨骼动画所在的命名空间为spine。
骨骼动画的类为SkeletonAnimation。
创建骨骼动画
static SkeletonAnimation* SkeletonAnimation::createWithFile (const std::string& skeletonDataFile,const std::string& atlasFile,floatscale= 1);
skeletonDataFile:动画配置文件
atlasFile:纹理配置文件
spTrackEntry* setAnimation(int trackIndex,const std::string&name,boolloop);
spTrackEntry* addAnimation (int trackIndex,const std::string& name, boolloop, floatdelay= 0);
trackIndex动画管道,一般用0,只有一个动画管道,可以设置多个,这样会同时播放多个动画。可以把头和身体分开,使用两个管道,这样头和身体可以单独控制。
name动画的名字
loop是否循环播放,如果设为ture后续在添加的动画则不会播放
delay延迟多次时间播放
例:
SkeletonAnimation*node=SkeletonAnimation::createWithFile("JXM.json","JXM.atlas",0.5f);
auto track=node->setAnimation(0,"in",false);//设置0好管道的第一个动画,
auto track1=node->addAnimation(0,"clk_1",true,1);// 往0号管道后面来添加,用add
动画事件
//当管道中动画切换到另一个动画时调用,管道内第一个动画不会调用
void setStartListener(const StartListener& listener);
//管道中有动画完成播放时调用
void setEndListener(const EndListener& listener);
//管道中最后一个动画完成一次调用,最后一个为循环动画时会多次调用
void setCompleteListener(const CompleteListener& listener);
//动画中内部有事件发生
void setEventListener(const EventListener& listener);
例如(设置了15秒后会结束动画):
SkeletonAnimation* node=SkeletonAnimation::createWithFile("JXM.json","JXM.atlas",0.5f);
autotrack= node->setAnimation(0,"in", false); //设置0好管道的第一个动画,
autotrack1= node->addAnimation(0,"clk_1", false,0.3); // 往0号管道后面来添加,用add
autotrack2= node->addAnimation(0,"clk_1", true,1);
node->setStartListener([](intn){ log("setStartListener%d", n); });
node->setEndListener([](intn){ log("setEndListener%d", n); });
node->setCompleteListener([](intn,intl){ log("setCompleteListener%d, %d", n, l);});
结果:
spTrackEntry*entry为setAnimation或addAnimation的返回值
//切换到动画时调用,管道内第一个动画不会调用
void setTrackStartListener(spTrackEntry*entry,const StartListener&listener);
//管道中有动画结束
void setTrackEndListener(spTrackEntry*entry,const EndListener&listener);
//最后一个动画完成一次,最后一个为循环动画时会多次调用
void setTrackCompleteListener(spTrackEntry*entry,const CompleteListener&listener);
//动画中有事件发生
void setTrackEventListener(spTrackEntry*entry,const EventListener&listener);
事件函数的调用总是先调用TrackXXXListener,然调用XXXXListener函数。
结束骨骼动画
以下两个函数,可以结束骨骼动画:
voidSkeletonAnimation::clearTracks ();
voidSkeletonAnimation::clearTrack (inttrackIndex= 0);
非循环播放的动画可以在EndListener中直接删除如动画节点。果循环播放的动画要结束,然后删除动画,可以在setCompleteListener事件中调用clearTracks/ clearTrack函数,然后移除节点。
不能在setTrackCompleteListener事件中调用clearTracks函数,因为在调用clearTracks/ clearTrack函数时,释放Track中所有数据,包括listener的Lambda表达式,当程序运行setCompleteListener中的CompleteListener的lambada表达式时,会导致崩溃。
也不能在EndListener事件和TrackEndListener事件中调用clearTracks/ clearTrack函数这个函数内部会调用EndListener/TrackEndListener,从而导致死循环。
例:
autotrack= node->setAnimation(0,"out", false);
node->setCompleteListener([node](intn,intl){
log("leave %d, %d", n,l);
node->clearTrack(0);
node->retain();//还在执行node节点的代码,对象不能被释放
node->autorelease();//加入释放池,绘制完节点后再释放。
node->removeFromParent();});