cocos2dx的动画有很多种方式,不过今天介绍的就是其中一种,AnimatePacker结合TexturePacker完成的动画,其实还有一种zwoptex是和texturepacker一样的工具,都可以生成一个plist和整合的png,但是texturepacker是个要收费的东西,我使用免费版本,一直以为不错,结果后来发现,他会在生成的图片上画一个很大的红印,这也许就是免费版的坑爹之处了,所以最好使用破解版。
而texturepacker是个很好用的工具,一看基本就知道怎么用了。也就不再介绍了,它可以将所有的小图片生成一个大的图片,以及一个plist文件,plist文件中纪录各个小图片的位置和尺寸,以及是什么工具生成的,是texturepacker还是zwoptex。不过最重要的还是plist文件中图片属性的数组。
如果你需要整个plist里面的所有图片,那么就可以直接使用这个plist和png就可以实现整个动画了,但是如果你需要将这个plist里面的动画分批次,或者分组的话,那么就需要使用animatepacker这个工具了,这也是一个很好用的东西,直接将plist拖入animatepacker左上方的plist区域,然后可以看到右方sprite区域里面列出了所有的sprite,texturepacker生成的plist能很好的辨认,zwoptex会比较混乱,所以使用了texturepacker,左下方可以直接设置每个动作的名字,时间,还有方向,最好不要有空格之类的名字,因为代码里面要用到。
这个时候该代码出场了。因为版本问题稍微有些修改,我使用的cocos2dx 2.2,而且由于需要使用的名字,另外还使用了一些修改。
需要使用到一个AnimatePacker类,使用方法如下:
char anuPath[256];
sprintf(anuPath,"%d", skillId);
string actionType = anuPath;
sprintf(anuPath,"assets/skill/icon/%d/%d.xml",skillId, skillId);
CCDictionary *dict =AnimatePacker::getInstance()->loadAnimations(anuPath);
CCString *str = (CCString *)dict->objectForKey(actionType);
CCSprite *sprite =CCSprite::createWithSpriteFrameName(getFirstPictureNameByType(actionType, anuPath).c_str());
m_skillShow->setTexture(sprite->getTexture());
CCCallFunc *endFun = CCCallFunc::create(this,callfunc_selector(RoleSprite::showSkillEnd));
CCSequence *seq =CCSequence::create(AnimatePacker::getInstance()->getAnimate(actionType.c_str()), endFun, NULL);
m_skillShow->runAction(seq);
AnimatePacker::getInstance()->freeAnimations(anuPath);
解释一下,就是先让animatepacker去load所有的动作,会得到所有动作的第一张图片的名字的一个dictionary,然后使用spriteframename的方式创建一个sprite,接着就象使用普通的sprite那样就可以了。
而loadAnimations里面所使用的原理,我们也可以使用zwoptex的那种方式。就是光有plist和png文件的时候,loadAnimations函数的参数是那个xml的相对路径,而xml里面有plist的名字,然后找到该文件的相对路径,依次添加到CCSpriteFrameCache::sharedSpriteFrameCache()里面,如下方式:
string plistPath =CCFileUtils::sharedFileUtils()->fullPathFromRelativeFile(plists[i].c_str(), pszPath.c_str());
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile(plistPath.c_str());
我写了一个sprite的动画sprite:
//
// frameMoveSprite.h
// shengxiao
//
// Created by liuyun on 10/25/13.
//
//
#ifndef __shengxiao__frameMoveSprite__
#define __shengxiao__frameMoveSprite__
#include "cocos2d.h"
using namespace cocos2d;
class LYFrameMoveSprite:publicCCSprite {
virtualbool initWithFile(constchar *pszFilename,CCPoint position, int count, float time);
virtual ~LYFrameMoveSprite();
int imgsCount;
float changeTime;
CCSize maxSize;
public:
CCSize getMaxSize();
LYFrameMoveSprite();
staticLYFrameMoveSprite* create(constchar *pszFileName,CCPoint position, int count, float time);
};
#endif /* defined(__shengxiao__frameMoveSprite__) */
//
// frameMoveSprite.cpp
// shengxiao
//
// Created by liuyun on 10/25/13.
//
//
#include "frameMoveSprite.h"
using namespace std;
/*
*param:
* fileName:相对路径文件名 plist以及image相同名称
* position:图片展示位置点,中心为锚点
* count:item image count
* time:图片切换时间 second
*/
LYFrameMoveSprite*LYFrameMoveSprite::create(constchar *fileName, CCPoint position,int count,float time)
{
LYFrameMoveSprite *pobSprite =newLYFrameMoveSprite();
if (pobSprite && pobSprite->initWithFile(fileName, position, count, time))
{
pobSprite->autorelease();
return pobSprite;
}
CC_SAFE_DELETE(pobSprite);
return NULL;
}
LYFrameMoveSprite::LYFrameMoveSprite()
{
}
LYFrameMoveSprite::~LYFrameMoveSprite()
{
}
CCSize LYFrameMoveSprite::getMaxSize()
{
returnmaxSize;
}
boolLYFrameMoveSprite::initWithFile(constchar *fileName, CCPoint position,int count,float time)
{
CCAssert(fileName !=NULL,"Invalid filename for sprite");
if (!CCSprite::init()) {
return false;
}
changeTime = time;
imgsCount = count;
//xianhe animation :
//step 1: create spriteFrameCach by loading png picture and plist;
string imgFile = fileName;
imgFile.append(".png");
string plistFile = fileName;
plistFile.append(".plist");
CCTexture2D *xianheTexture =CCTextureCache::sharedTextureCache()->addImage(imgFile.c_str());
CCSpriteFrameCache *cache =CCSpriteFrameCache::sharedSpriteFrameCache();
cache->addSpriteFramesWithFile(plistFile.c_str(), xianheTexture);
// step 2: ceate spriteSheet obj,and set new position
CCSpriteBatchNode *xianheSheet=CCSpriteBatchNode::createWithTexture(xianheTexture);
xianheSheet->setPosition(position);
addChild(xianheSheet);
// step 3: create animation frame, load sub-picture of animation frame from cache, and add it as animation frame
//then run with sprite interface;
CCArray* animFrames =CCArray::create();
string fPngName = fileName;
int pos = fPngName.find_last_of("/");
fPngName = fPngName.substr(pos +1);
string midName = fPngName;
midName.append("_0000.png");
// string midName = "n_0000.png";
CCSprite* xianheSprite =CCSprite::createWithSpriteFrameName(midName.c_str());
char xianheStr[64] = {0};
for(int k =0; k <imgsCount; k++)
{
sprintf(xianheStr,"%s_%04d.png", fPngName.c_str(), k);
CCSpriteFrame *frame = cache->spriteFrameByName(xianheStr);
CCSize frameSize = frame->getTexture()->getContentSize();
if ((frameSize.width * frameSize.height) > (maxSize.width *maxSize.height)) {
maxSize = frameSize;
}
animFrames->addObject(frame);
}
CCAnimation *animation =CCAnimation::createWithSpriteFrames(animFrames,changeTime);
xianheSprite->runAction(CCRepeatForever::create(CCAnimate::create(animation) ));
xianheSheet->addChild(xianheSprite);
// don't release here.
// when load texture failed, it's better to get a "transparent" sprite then a crashed program
// this->release();
return true;
}
看代码即可知道,如何实现动画了。
下面是animatepacker库的代码:
SingletonAni.h
#ifndef __SINGLETON_H__
#define __SINGLETON_H__
template <typename T>
class SingletonAnimate
{
public:
inlinestatic T* getInstance();
inlinestatic void release();
private:
static T* t;
};
template <typename T>
inline T*SingletonAnimate<T>::getInstance()
{
if (!t)
{
t =new T;
}
returnt;
}
template<typename T>
inlinevoid SingletonAnimate<T>::release()
{
if (t)
{
deletet;
t =0;
}
}
template <typename T>
T* SingletonAnimate<T>::t =0;
#endif // __SINGLE_H__
AnimatePacker.h
#ifndef _ANIMATE_PACKER_H_
#define _ANIMATE_PACKER_H_
#include <string>
#include <map>
#include <vector>
#include <set>
#include "cocos2d.h"
#include "SingletonAni.h"
struct Animate{
std::string name;
float delay;
bool flipX;
bool flipY;
std::vector<std::string> spriteFrames;
};
class AnimatePacker:publicSingletonAnimate<AnimatePacker>
{
public:
cocos2d::CCDictionary * loadAnimations(constchar *path);
void freeAnimations(constchar *path);
//Using this function to getting original animate(without FilpX and FlipY).
cocos2d::CCAnimate* getAnimate(constchar *name);
//This function supports FlipX and FlipY.
cocos2d::CCSequence* getSequence(constchar *name);
private:
//The two functions is came from Timothy Zhang. Thank him for his share.
//Original Tip Link:http://www.cocos2d-x.org/boards/6/topics/7219
cocos2d::CCSequence *createSequence(cocos2d::CCArray *actions);
cocos2d::CCSequence *createSequence(cocos2d::CCFiniteTimeAction *pAction1, cocos2d::CCFiniteTimeAction *pAction2, ...);
//From animate name to CCAnimates
std::map<std::string,Animate> nameToAnimateMap;
//From xml path to plist names
std::map<std::string,std::vector<std::string> > pathToPlistsMap;
//From xml path to animate names
std::map<std::string,std::set<std::string> > pathToNameMap;
};
#endif//_ANIMATE_PACKER_H_
#include "AnimatePacker.h"
#include "platform/CCSAXParser.h"
using namespace std;
using namespace cocos2d;
class AnimateSaxDelegator :public CCSAXDelegator
{
public:
enum{
STATE_NONE,
STATE_PLIST,
STATE_ANIMATION,
STATE_NAME,
STATE_DELAY,
STATE_FLIP_X,
STATE_FLIP_Y,
STATE_SPRITE_FRAME
}state;
void startElement(void *ctx,const char *name,const char **atts) ;
void endElement(void *ctx,const char *name) ;
void textHandler(void *ctx,const char *s,int len) ;
vector<string> plists;//All plist name
vector<Animate> animates;//All animate data
};
voidAnimateSaxDelegator::startElement( void *ctx, const char *name, const char **atts )
{
string tag((char*)name);
if (tag=="plist")
{
state=STATE_PLIST;
}
elseif (tag=="animation")
{
state=STATE_ANIMATION;
animates.push_back(Animate());
}
elseif (tag=="name")
{
state=STATE_NAME;
}
elseif (tag=="delay")
{
state=STATE_DELAY;
}
elseif (tag=="spriteFrame")
{
state=STATE_SPRITE_FRAME;
}
elseif (tag=="flipX")
{
state=STATE_FLIP_X;
}
elseif (tag=="flipY")
{
state=STATE_FLIP_Y;
}
else
{
state=STATE_NONE;
}
}
voidAnimateSaxDelegator::endElement( void *ctx, const char *name )
{
string tag((char*)name);
if (tag=="plist")
{
}
elseif (tag=="animation")
{
}
elseif (tag=="name")
{
}
elseif (tag=="delay")
{
}
elseif (tag=="spriteFrame")
{
}
elseif (tag=="flipX")
{
}
elseif (tag=="flipY")
{
}
else
{
}
state =STATE_NONE;
}
voidAnimateSaxDelegator::textHandler( void *ctx, const char *ch, int len )
{
if (state ==STATE_NONE)
{
return;
}
string text((char*)ch,0,len);
int index;
float delay;
switch (state)
{
caseSTATE_PLIST:
plists.push_back(text);
break;
caseSTATE_ANIMATION:
break;
caseSTATE_NAME:
index=animates.size()-1;
animates[index].name=text;
break;
caseSTATE_DELAY:
index=animates.size()-1;
delay=atof(text.c_str());
animates[index].delay=delay;
break;
caseSTATE_SPRITE_FRAME:
index=animates.size()-1;
animates[index].spriteFrames.push_back(text);
break;
caseSTATE_FLIP_X:
index=animates.size()-1;
animates[index].flipX=(text=="true");
break;
caseSTATE_FLIP_Y:
index=animates.size()-1;
animates[index].flipY=(text=="true");
break;
default:
break;
}
}
CCDictionary *AnimatePacker::loadAnimations(constchar *path )
{
CCDictionary *ret =CCDictionary::create();
string pszPath =CCFileUtils::sharedFileUtils()->fullPathForFilename(path);
//CCFileUtils::sharedFileUtils()->fullPathForFilename(path);
CCSAXParser parser;
AnimateSaxDelegator delegator;
if (false == parser.init("UTF-8"))
{
//TODO
return ret;
}
parser.setDelegator(&delegator);
parser.parse(pszPath.c_str());
//load plist
vector<string> plists=delegator.plists;
for (unsignedint i=0;i<plists.size();i++)
{
string plistPath =CCFileUtils::sharedFileUtils()->fullPathFromRelativeFile(plists[i].c_str(), pszPath.c_str());//CCFileUtils::sharedFileUtils()->fullPathFromRelativeFile(plists[i].c_str(), pszPath);
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile(plistPath.c_str());
}
//load animate
vector<Animate> animates=delegator.animates;
CCArray *spriteFramesArray =new CCArray();
set<string> animateNames;
for (unsignedint i=0;i<animates.size();i++)
{
Animate animate=animates[i];
vector<string> spriteFrames=animate.spriteFrames;
CCString *str = CCString::create(animate.spriteFrames[0]);
CCLog("str->getCString() = %s, animate.name.c_str() = %s", str->getCString(), animate.name.c_str());
if (animate.spriteFrames[0].length() >0) {
ret->setObject(str, animate.name.c_str());
}
for (unsignedint j=0;j<spriteFrames.size();j++)
{
animateNames.insert(spriteFrames[j]);
CCLOG("spriteFrames[j] = %s", spriteFrames[j].c_str());
CCSpriteFrame *spriteFrame=CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(spriteFrames[j].c_str());
// CCLOG("spriteFrame = %p", spriteFrame);
spriteFramesArray->addObject(spriteFrame);
}
CCAnimation *animation =CCAnimation::createWithSpriteFrames(spriteFramesArray, animate.delay);//CCAnimation::createWithSpriteFrames(spriteFramesArray,animate.delay);
CCAnimationCache::sharedAnimationCache()->addAnimation(animation,animate.name.c_str());
spriteFramesArray->removeAllObjects();
}
//record animate
for(unsignedint i=0;i<animates.size();i++){
Animate animate=animates[i];
nameToAnimateMap[animate.name]=animate;
}
//record plist
pathToPlistsMap[path]=plists;
//record CCAnimate name
pathToNameMap[path]=animateNames;
return ret;
}
CCAnimate*AnimatePacker::getAnimate(constchar *name )
{
CCAnimation* animation=CCAnimationCache::sharedAnimationCache()->animationByName(name);
if(animation)
{
returnCCAnimate::create(animation);
}
returnNULL;
}
voidAnimatePacker::freeAnimations(constchar *path)
{
string pszPath =CCFileUtils::sharedFileUtils()->fullPathForFilename(path);
vector<string> plists=pathToPlistsMap[path];
for (unsignedint i=0;i<plists.size();i++)
{
string plistPath =CCFileUtils::sharedFileUtils()->fullPathFromRelativeFile(plists[i].c_str(), pszPath.c_str());
CCSpriteFrameCache::sharedSpriteFrameCache()->removeSpriteFramesFromFile(plistPath.c_str());
}
pathToPlistsMap.erase(path);
set<string> animateNames=pathToNameMap[path];
for (set<string>::iterator strItr=animateNames.begin();strItr!=animateNames.end();++strItr)
{
CCAnimationCache::sharedAnimationCache()->removeAnimationByName((*strItr).c_str());
nameToAnimateMap.erase((*strItr));
}
pathToNameMap.erase(path);
}
CCSequence*AnimatePacker::getSequence(constchar *name){
CCAnimation* animation=CCAnimationCache::sharedAnimationCache()->animationByName(name);
if(animation)
{
Animate animate=nameToAnimateMap[name];
CCArray *actions=CCArray::create();
actions->addObject(CCFlipX::create(animate.flipX));
actions->addObject(CCFlipY::create(animate.flipY));
actions->addObject(CCAnimate::create(animation));
CCSequence *sequence=createSequence(actions);
actions->removeAllObjects();
return sequence;
}
returnNULL;
}
CCSequence *AnimatePacker::createSequence(CCArray *actions)
{
CC_ASSERT(actions->count()>1);
CCSequence *seq =CCSequence::createWithTwoActions((CCFiniteTimeAction*)actions->objectAtIndex(0),
(CCFiniteTimeAction*)actions->objectAtIndex(1));
for (unsignedint i = 2; i < actions->count(); ++i) {
seq =CCSequence::createWithTwoActions(seq, (CCFiniteTimeAction*)actions->objectAtIndex(i));
}
return seq;
}
CCSequence *AnimatePacker::createSequence(CCFiniteTimeAction *pAction1,CCFiniteTimeAction *pAction2, ...)
{
va_list params;
va_start(params, pAction2);
CCSequence *pPrev =CCSequence::createWithTwoActions(pAction1, pAction2);
CCFiniteTimeAction *pNow =NULL;
while( pPrev ) {
pNow =va_arg(params, CCFiniteTimeAction*);
if (pNow)
{
pPrev =CCSequence::createWithTwoActions(pPrev, pNow);
}
else
{
break;
}
}
va_end(params);
return pPrev;
}
至于已经有sprite了,为什么还要添加CCSpriteBatchNode,可以参看参考资料,
CCSpriteBatchNode 中的所有CCSprite只会被渲染1次,因此可以提高游戏的FPS。
限制:加入到 CCSpriteBatchNode 中的CCSprite必须使用同一张纹理图,尺寸应该相同,否则会显示不出来。