一、帧动画的创建过程
帧动画的实现有四个不可或缺的类,如下:
1.CCSpriteFrame:精灵帧信息。存储帧动画的每一帧的纹理基本信息。
class CC_DLL CCSpriteFrame : public CCObject
{
public:
//函数略...
protected:
CCPoint m_obOffset; // 去掉纹理块空白后所导致的锚点偏移
CCSize m_obOriginalSize; // 纹理块的原始大小(未去掉空白的大小)
CCRect m_obRectInPixels; // 纹理块的大小(像素单位)
bool m_bRotated; // 矩形块是否旋转
CCRect m_obRect; // 纹理块
CCPoint m_obOffsetInPixels; // 去掉纹理块空白后所导致的锚点偏移(像素单位)
CCSize m_obOriginalSizeInPixels; // 纹理块的原始大小(像素单位)
CCTexture2D *m_pobTexture; // 图集纹理
std::string m_strTextureFilename; // 对应的图集图片
};
2.CCAnimationFrame:序列帧动画单帧信息,我们把它叫做动画帧。存储了精灵帧信息和单帧信息(持续帧数和附加数据)。
class CC_DLL CCAnimationFrame : public CCObject
{
public:
//函数略... // 定义一个精灵帧成员指针变量m_pSpriteFrame,代表当前动画帧所对应的精灵帧。
CC_SYNTHESIZE_RETAIN(CCSpriteFrame*, m_pSpriteFrame, SpriteFrame) // 注意:
// 此序列帧动画单帧持续的帧数(如果m_fDelayUnits为5,则这一动画帧连续播放5帧)
CC_SYNTHESIZE(float, m_fDelayUnits, DelayUnits) // 定义一个词典成员指针变量m_pUserInfo
CC_SYNTHESIZE_RETAIN(CCDictionary*, m_pUserInfo, UserInfo)
};
3.CCAnimation:序列帧动画信息。存储了帧动画需要的所有单帧信息和对单帧信息的管理。
class CC_DLL CCAnimation : public CCObject
{
public:
//函数略... // 总帧数
// 只读属性,不能人为设置
// 因为 总帧数为所有动画帧的持续帧数之和
// m_fTotalDelayUnits = animationframe1->getgetDelayUnits() + animationframe2->getgetDelayUnits() + ...
// 注意:这里的总帧数和图片数是两个概念,一幅图片可能连着播放多帧,
// 帧:刷新的次数单位
// 动画帧:对应一幅图片
CC_SYNTHESIZE_READONLY(float, m_fTotalDelayUnits, TotalDelayUnits) // 每两帧间的时间间隔
CC_SYNTHESIZE(float, m_fDelayPerUnit, DelayPerUnit) // 动画总时间
CC_PROPERTY_READONLY(float, m_fDuration, Duration) // 保存所有动画帧
CC_SYNTHESIZE_RETAIN(CCArray*, m_pFrames, Frames) // 是否在动画结束时恢复至初始帧
CC_SYNTHESIZE(bool, m_bRestoreOriginalFrame, RestoreOriginalFrame) // 循环播放次数
CC_SYNTHESIZE(unsigned int, m_uLoops, Loops)
};
4.CCAnimate:序列帧动画处理类。利用序列帧动画信息类CCAnimation运行动画。
class CC_DLL CCAnimate : public CCActionInterval
{
public: // 初始化动画
bool initWithAnimation(CCAnimation *pAnimation); // 重载基类的相应函数
virtual CCObject* copyWithZone(CCZone* pZone);
virtual void startWithTarget(CCNode *pTarget);
virtual void stop(void);
virtual void update(float t);
virtual CCActionInterval* reverse(void); public:
// 创建一个序列帧动画,内部调用create实现。参数为序列帧动画信息结构指针。
CC_DEPRECATED_ATTRIBUTE static CCAnimate* actionWithAnimation(CCAnimation *pAnimation);
// 创建一个序列帧动画。
static CCAnimate* create(CCAnimation *pAnimation);
// 定义一个序列帧动画信息结构指针变量以及存取此变量的函数。
CC_SYNTHESIZE_RETAIN(CCAnimation*, m_pAnimation, Animation)
protected:
// 保存 切换到每个动画帧时 当前相对于本次循环的进度比例(表示为0~1.0之间的浮点数,0表示未开始,0.5表示进行到一半,1.0表示完成)
// 是控制动画播放的标志
std::vector<float>* m_pSplitTimes;
// 本次循环 当前要播放的下一帧序号。
int m_nNextFrame;
// 初始帧
CCSpriteFrame* m_pOrigFrame;
// 已执行的循环次数
unsigned int m_uExecutedLoops;
};
不论是用什么样的方法创建帧动画,本质的流程都是这样的(参考文章末尾图片):
CCSpriteFrame* spriteframe = CCSpriteFrame::create(...);
CCAnimationFrame* animationframe = new CCAnimationFrame;
animationframe->initWithSpriteFrame(spriteframe,...);
CCAnimation* animation = CCAnimation::create(...);
CCAction * action = CCAnimate::create(animation);
创建完成
二、帧动画的播放过程
重点介绍CCAnimate类的两个函数initWithAnimation和update函数。
1.initWithAnimation函数
在这个函数中重点注意m_pSplitTimes的赋值,因为在执行帧动画的函数update中m_pSplitTimes是播放帧动画的重要标志。
在initWithAnimation函数中,将每个动画帧对应的执行进度按对应顺序插入到m_pSplitTimes中
bool CCAnimate::initWithAnimation(CCAnimation *pAnimation)
{
CCAssert( pAnimation!=NULL, "Animate: argument Animation must be non-NULL"); // 取得每次执行序列的时长
float singleDuration = pAnimation->getDuration(); // 乘以循环次数做为当前动画总时长来进行初始化
if ( CCActionInterval::initWithDuration(singleDuration * pAnimation->getLoops() ) )
{
m_nNextFrame = ;
setAnimation(pAnimation);
m_pOrigFrame = NULL;
m_uExecutedLoops = ; // 设置容器大小
m_pSplitTimes->reserve(pAnimation->getFrames()->count()); float accumUnitsOfTime = ;
// 每帧时间
float newUnitOfTimeValue = singleDuration / pAnimation->getTotalDelayUnits(); CCArray* pFrames = pAnimation->getFrames();
CCARRAY_VERIFY_TYPE(pFrames, CCAnimationFrame*); CCObject* pObj = NULL;
CCARRAY_FOREACH(pFrames, pObj)
{
CCAnimationFrame* frame = (CCAnimationFrame*)pObj;
// 当前动画帧的进度(相对于每次循环的总时间) = 从开始到当前动画帧的总时间之和 / 每次循环的总时间
// 从开始到当前动画帧的总时间之和 = 从开始到当前动画帧的总帧数 * 每帧间隔时间
float value = (accumUnitsOfTime * newUnitOfTimeValue) / singleDuration;
accumUnitsOfTime += frame->getDelayUnits();
// 当前动画帧的进度到m_pSplitTimes
m_pSplitTimes->push_back(value);
}
return true;
}
return false;
}
2.update函数
在这个函数中利用initWithAnimation函数赋值过的m_pSplitTimes来控制帧动画播放。
// 帧动画的执行函数
// 参数t表示一次循环animation执行进度(哪次不确定,由update内部计算得知)
//(表示为0~1.0之间的浮点数,0表示未开始,0.5表示进行到一半,1.0表示完成)
void CCAnimate::update(float t)
{
// 计算循环是否够次数
if( t < 1.0f ) {
// 计算当前进度播放的循环数
t *= m_pAnimation->getLoops(); // 通过先取整再判断是否大于当前的已经循环次数来判断是否是新的循环,
// 如果是将下一帧置零,已经循环的次数加1
unsigned int loopNumber = (unsigned int)t;
if( loopNumber > m_uExecutedLoops ) {
m_nNextFrame = ;
m_uExecutedLoops++;
} // 对t进行浮点取模值,将其限制在0~1之间,
// 这样等于又转换成为了当前动画播放的进度值。
t = fmodf(t, 1.0f);
} // 取得动画的帧信息容器和动画帧个数
CCArray* frames = m_pAnimation->getFrames();
unsigned int numberOfFrames = frames->count();
// 精灵图片信息
CCSpriteFrame *frameToDisplay = NULL; // 找出要播放的帧图片设置为精灵要显示的图片。
// 方法:从下一帧开始到结束帧进行遍历,判断是否到了这一帧。
for( unsigned int i=m_nNextFrame; i < numberOfFrames; i++ ) {
// 取出循环中的当前动画帧的播放进度
float splitTime = m_pSplitTimes->at(i);
// 如果这一帧的进度小于当前动画的播放进度,即代表进入了这一帧。
if( splitTime <= t ) {
// 取得对应的动画帧信息。
CCAnimationFrame* frame = (CCAnimationFrame*)frames->objectAtIndex(i);
// 取得当前帧的精灵图片信息。
frameToDisplay = frame->getSpriteFrame();
// 这才是显示动画的关键,找到相应的精灵帧并设置为m_pTarget要显示的当前帧。
((CCSprite*)m_pTarget)->setDisplayFrame(frameToDisplay);
// 通过动画帧信息取得其附加的用户词典信息,这个词典存储的是用于需要通知的目标。
CCDictionary* dict = frame->getUserInfo();
if( dict )
{
// 忽略
}
// 帧数加一。
m_nNextFrame = i+; break;
}
}
}
三、最后用一张图来总结帧动画的创建和播放
欢迎大家学习、共享,如果文章中有错误或漏洞,请大家在评论区留言!!