本文为xiedantibu原创,转载请注明作者及出处!
###目录
—-
AnimationFrame
Animation-序列帧动画信息
AnimationCache
Animate-帧动画处理类
知识点补漏
在cocos2d-x-SpriteFrameCache中的plist文件格式中我们谈到,plist文件还将用在CCAnimationCache中。所以今天接着前面的章节,继续学习CCAnimationCache,说说帧的用法。
####AnimationFrame-动画帧
—
AnimationFrame动画帧实际上就是精灵帧加上延迟时间就是所谓的动画帧了,其实也是啊。单独一张帧,给他一个时间就是动画了啊。。。
先看下AnimationFrame的主要属性把:
SpriteFrame* _spriteFrame;//话说这就是精灵帧啊
float _delayUnits;//单帧延迟时间,其实在目前的代码中,都是以1.0去处理他的,如果改为其他值,可能会出现问题,所以在AnimationFrame中帧的延迟时间,要以默认值去处理,话说放在这就是个多余的角色
ValueMap _userInfo;//用户信息啊,这杂用呢,跟EventCustom合伙一起用
下面接着看下DisplayedEventInfo这个结构把,主要是动画帧在显示(display)的时候,把AnimationFrameDisplayedNotification这个消息分发出去,当然dispatchEvent的时候要设置setUserData
,而这UserData就是DisplayedEventInfo。当然分发的消息,也就有接受消息的,具体还是学习ActionsTest中的Animation例子。下面就先看下DisplayedEventInfo:
struct DisplayedEventInfo
{
Node* target;//目标精灵
const ValueMap* userInfo;//用户信息
}; DisplayedEventInfo主要在显示动画帧的时候,传递消息,在`Animate::update`中学习到,到时候具体再温习学习吧,现在先记下来。
动画帧的构造方法
static AnimationFrame* create(SpriteFrame* spriteFrame, float delayUnits, const ValueMap& userInfo)//主要调用initWithSpriteFrame方法
初始化动画帧
bool initWithSpriteFrame(SpriteFrame* spriteFrame, float delayUnits, const ValueMap& userInfo);//主要是给精灵帧,单帧延迟时间,以及用户消息赋值
设置获取精灵帧
SpriteFrame* getSpriteFrame()//获取当前动画帧的精灵帧
void setSpriteFrame(SpriteFrame* frame)//设置当前动画帧的精灵帧
设置获取单帧延迟时间
float getDelayUnits() const { return _delayUnits; };
void setDelayUnits(float delayUnits);
设置获取用户信息,用户信息主要是为了在显示动画帧的时候,给DisplayedEventInfo的ValueMap* userInfo赋值。
const ValueMap& getUserInfo() const
void setUserInfo(const ValueMap& userInfo) --- <a id='Animation' name='Animation'> </a> ####[Animation-动画帧信息类](#top) --- Animation英文解释就是动画,但其实他不是真正的执行者,他只能算是动画的本体。真正的动画执行是Animate这个类,如`sprite->runAction(Animate::create(animation));`有这代码我们可以看出Animate才是Action,而Animation其实就是保存了一堆的动画帧。要了解Animation首先得看它的属性:
float _totalDelayUnits;//_totalDelayUnits这东西英文翻译是动画的总延迟单元,但理解成总帧数我觉得更恰当
float _delayPerUnit;//在Animation中的动画帧的延迟时间都是一样的,就是这东东。延迟单元时间
float _duration;//播放时间:_totalDelayUnits*_delayPerUnit
Vector<AnimationFrame*> _frames;//该Vector是封装了std::vector<T> _data的容器,保存到Vector中的数据不需要管内存释放问题。所以_frames也就是一个保存了AnimationFrame*动画帧指针的数组。
bool _restoreOriginalFrame;//当动画结束的时候,释放还原到原来的帧
unsigned int _loops;//循环几次啊。。。 初始化相关方法:
bool init();//该方法主要就初始化了_loops以及_delayPerUnit两个属性。
bool initWithSpriteFrames(const Vector<SpriteFrame*>& arrayOfSpriteFrameNames, float delay = 0.0f, unsigned int loops = 1);//该方法名字就很突出,通过精灵帧来初始化,看参数第一个就是精灵帧数组,第二个参数有个默认值,延迟时间默认为0.0,第三个参数是默认的循环次数1。从第一个参数我们能明白,该方法主要是通过遍历数组,将精灵帧组装成动画帧,再把动画帧加入到_frames中,对了在加入到_frames的时候,要更新_totalDelayUnits++,该方法第二个参数是真正的赋值给单位延迟时间的。
bool initWithAnimationFrames(const Vector<AnimationFrame*>& arrayOfAnimationFrameNames, float delayPerUnit, unsigned int loops);//该方法与上面的方法相似,但是他的第一个参数却是动画帧,所以他不需要转换,直接加入到_frames中,但在加入的同时也得更新_totalDelayUnits,不过这里却写成_totalDelayUnits += animFrame->getDelayUnits(),个人理解觉得还是得改成_totalDelayUnits++,应该是笔误吧。
Create相关方法:
static Animation* create(void);//回调init方法初始化
static Animation* createWithSpriteFrames(const Vector<SpriteFrame*>& arrayOfSpriteFrameNames, float delay = 0.0f, unsigned int loops = 1);//回调initWithSpriteFrames方法初始化,主要是精灵帧,还有delay
static Animation* create(const Vector<AnimationFrame*>& arrayOfAnimationFrameNames, float delayPerUnit, unsigned int loops = 1);//回调initWithAnimationFrames方法初始化,注意该方法中的_totalDelayUnits可能有问题,只要加入的动画帧的_delayPerUnit不为1,就可能出问题
将精灵帧添加到动画帧数组_frames中:
void Animation::addSpriteFrame(SpriteFrame* spriteFrame)
{
AnimationFrame *animFrame = AnimationFrame::create(spriteFrame, 1.0f, ValueMap());//精灵帧转换成动画帧
_frames.pushBack(animFrame);//加入到数组中
_totalDelayUnits++;//总帧数update
} 将图片添加到动画帧数组_frames中:
void Animation::addSpriteFrameWithFile(const std::string& filename)
{
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);//先将图片转换成纹理
Rect rect = Rect::ZERO;
rect.size = texture->getContentSize();
SpriteFrame *frame = SpriteFrame::createWithTexture(texture, rect);//把纹理转换成精灵帧
addSpriteFrame(frame);//回调上一个方法
}
将纹理添加到动画帧数组_frames中:
void addSpriteFrameWithTexture(Texture2D* pobTexture, const Rect& rect);//直接添加纹理加入到动画帧数组_frames
获取播放时间:
float Animation::getDuration(void) const
{
return _totalDelayUnits * _delayPerUnit;
}
设置是否动画播放完,还原原始帧:
bool getRestoreOriginalFrame()
void setRestoreOriginalFrame(bool restoreOriginalFrame)
####AnimationCache —
AnimationCache-动画缓存,当然就是保存Animation的东西了啊,下面我们来看看其相关属性啊。。。
Map<std::string, Animation*> _animations; //就是这个Map保存了Animation*缓存
static AnimationCache* s_sharedAnimationCache;//静态实例对象 下面就看其怎么实例化的啊
AnimationCache* AnimationCache::getInstance()
{
if (! s_sharedAnimationCache)//判断AnimationCache*是否为空
{
s_sharedAnimationCache = new (std::nothrow) AnimationCache();
s_sharedAnimationCache->init();
}
return s_sharedAnimationCache;
} 直接添加动画到_animations中
void addAnimation(Animation *animation, const std::string& name);//传入Animation,还需要一个名字,名字可以随便搞啊
根据名字把缓存中的动画移除
void removeAnimation(const std::string& name);//根据名字从Map中移除动画
通过读取plist文件,添加动画到Map中,由于这plist文件,不知道用的啥动画编辑器,所以就简单的说下啊
void addAnimationsWithFile(const std::string& plist);//把plist文件名传进去,回调下面方法
void addAnimationsWithDictionary(const ValueMap& dictionary,const std::string& plist);//紧接着上一个方法,传入ValueMap以及plist名字,跟进plist文件中format属性值不同解析,找到不同解析方法
void parseVersion1(const ValueMap& animations);//format值为1,在这方法中真正的添加动画到Map中
void parseVersion2(const ValueMap& animations);//format值为2
####Animate-帧动画处理类 — Animate继承于ActionInterval,那ActionInterval是啥呢,他就是一个持续性动作,那就是说Animate也是个持续性动作,而他的动作就是切换动画帧。
先看看他存了啥货色啊-属性
std::vector<float>* _splitTimes;//话说这东西就是个指针,废话,他不是指针是啥!!!它保存的就是一个个的(0,1/_totalDelayUnits,2/_totalDelayUnits,。。。),每一帧的进度
int _nextFrame;//当前要播放的下一帧序号
SpriteFrame* _origFrame;//各帧中保存的精灵信息
unsigned int _executedLoops;//循环次数
Animation* _animation;//保存动画帧信息的指针
EventCustom* _frameDisplayedEvent;//普通分发事件
AnimationFrame::DisplayedEventInfo _frameDisplayedEventInfo;//这东西很熟悉把,分发信息封装类,前面讲到的啊,有个sender,还有用户信息。
初始化方法
bool Animate::initWithAnimation(Animation* animation)//传了一个动画帧信息类进来
{
float singleDuration = animation->getDuration();//获取播放事件-_totalDelayUnits * _delayPerUnit(帧数*单帧延迟时间)
if ( ActionInterval::initWithDuration(singleDuration * animation->getLoops() ) )//这里要注意初始化持续性动作的时间,不是单纯的singleDuration,还需要Loops,这才是真正的总播放时间
{
_nextFrame = 0;//下一帧序号为0
setAnimation(animation);//初始化_animation,对啦,在这里讲下如果,单独回调setAnimation着方法的时候可能会出现问题,因为并没有改变赋值其他属性的值,所以在此注意
_origFrame = nullptr;//后面赋值,通过精灵获取其原始帧。
_executedLoops = 0;//当前循环次数
_splitTimes->reserve(animation->getFrames().size());//初始化_splitTimes存储的大小
float accumUnitsOfTime = 0;
float newUnitOfTimeValue = singleDuration / animation->getTotalDelayUnits();//就是_delayPerUnit
auto frames = animation->getFrames();//获取动画帧Vector
for (auto& frame : frames)迭代该Vector,给每一个动画帧,设置相应的进度
{
float value = (accumUnitsOfTime * newUnitOfTimeValue) / singleDuration;//其实就是accumUnitsOfTime/_totalDelayUnits
accumUnitsOfTime += frame->getDelayUnits();//其实frame->getDelayUnits()就是为1,如果不为1,可能出现问题啊
_splitTimes->push_back(value);//加入到vector中
}
return true;
}
return false;
}
创建一个Animate
Animate* Animate::create(Animation *animation)//直接传入一个动画帧信息类指针
{
Animate *animate = new (std::nothrow) Animate();
animate->initWithAnimation(animation);//回调初始化方法
animate->autorelease();
return animate;
} 开始并设置执行动画Node,该方法继承于Action
void Animate::startWithTarget(Node *target)
{
ActionInterval::startWithTarget(target);//给target赋值,执行动画的Node
Sprite *sprite = static_cast<Sprite*>(target);//看到这里我们应该明白,Animate的动画其实是针对精灵的
CC_SAFE_RELEASE(_origFrame);//回收原始帧
if (_animation->getRestoreOriginalFrame())//_restoreOriginalFrame为true,_restoreOriginalFrame表示如果动画播放结束,还原到原始帧
{
_origFrame = sprite->getSpriteFrame();//设置原始帧,直接获取精灵本身的帧
_origFrame->retain();//计数+1
}
_nextFrame = 0;//设置下一帧为0
_executedLoops = 0;//循环次数为0
} Animate停止播放
void Animate::stop()
{
if (_animation->getRestoreOriginalFrame() && _target)//_restoreOriginalFrame为true
{
static_cast<Sprite*>(_target)->setSpriteFrame(_origFrame);//精灵显示成初始化帧
}
ActionInterval::stop();
} 讲解update方法,在讲Animate的update之前,得先学习ActionInterval的step方法,这个step啥时候回调啊?在`ActionManager::update(float dt)`这方法回调。在update方法中我们看到`_currentTarget->currentAction->step(dt)`,看这就是Action的step方法。
void ActionInterval::step(float dt)
{
if (_firstTick)//_firstTick是一个控制变量,_firstTick为true只有在ActionInterval::initWithDuration的时候才设置为true
{
_firstTick = false;
_elapsed = 0;//_elapsed为从动作开始起逝去的时间
}
else
{
_elapsed += dt;//逝去的时间
}
this->update(MAX (0, // needed for rewind. elapsed could be negative
MIN(1, _elapsed /
MAX(_duration, FLT_EPSILON) // division by 0
)
)
);//MIN(1, _elapsed /MAX(_duration, FLT_EPSILON) )//从这看出不管_elapsed的时间是否大于1,则update(t)中的t都在(0,1)之间。
}
下面就来看看Animate::update这方法
void Animate::update(float t)//从上面方法我们就能看出t只能在(0-1)之间,当t=1的时候,说明动画已经放完了
{
// if t==1, ignore. Animation should finish with t==1
if( t < 1.0f ) {//表明动画还没放完
t *= _animation->getLoops();//t我们这里写成_elapsed /_duration,而_duration为一个循环的播放时间*_animation->getLoops(),所以t就是_elapsed/单次循环的播放时间*_animation->getLoops(),所以t*=_animation->getLoops(),最后的值t=_elapsed/单次循环的播放时间
// new loop? If so, reset frame counter
unsigned int loopNumber = (unsigned int)t;//这时候t可能大于1啊,而它的整数其实就是相当于实际播放的循环数
if( loopNumber > _executedLoops ) {//如果大于当前的循环播放值,则得更新当前的播放值
_nextFrame = 0;
_executedLoops++;
}
// new t for animations
t = fmodf(t, 1.0f);//// 对t进行浮点取模值,将其限制在0~1之间,这样等于又转换成为了当前动画播放的进度值
}
auto frames = _animation->getFrames();//获取动画帧Vector
auto numberOfFrames = frames.size();//有多少帧数
SpriteFrame *frameToDisplay = nullptr;//将要播放的帧
for( int i=_nextFrame; i < numberOfFrames; i++ ) {//注意这里是从_nextFrame开始迭代的
float splitTime = _splitTimes->at(i);//获取每一帧的播放进度
if( splitTime <= t ) {///如果这一帧的进度小于当前动画的播放进度,即代表进入了这一帧。,否则还是执行上一帧
AnimationFrame* frame = frames.at(i);//取得对应的动画帧信息
frameToDisplay = frame->getSpriteFrame();//取得当前帧的精灵图片信息。
static_cast<Sprite*>(_target)->setSpriteFrame(frameToDisplay);//找到精灵,设置显示的精灵帧,动画就是这样。。。
const ValueMap& dict = frame->getUserInfo();//用户信息
if ( !dict.empty() )
{
if (_frameDisplayedEvent == nullptr)
_frameDisplayedEvent = new (std::nothrow) EventCustom(AnimationFrameDisplayedNotification);//初始化分发事件
_frameDisplayedEventInfo.target = _target;
_frameDisplayedEventInfo.userInfo = &dict;
_frameDisplayedEvent->setUserData(&_frameDisplayedEventInfo);
Director::getInstance()->getEventDispatcher()->dispatchEvent(_frameDisplayedEvent);//分发时间
}
_nextFrame = i+1;//更新下一帧的播放序号
}
else {
break;
}
}
}
创建反向播放序列帧
Animate* Animate::reverse() const{
auto oldArray = _animation->getFrames();//获取全部的帧
Vector<AnimationFrame*> newArray(oldArray.size());//初始化大小为oldArray.size()的Vector
if (oldArray.size() > 0)//赋值了
{
for (auto iter = oldArray.crbegin(); iter != oldArray.crend(); ++iter)
{
AnimationFrame* animFrame = *iter;
if (!animFrame)
{
break;
}
newArray.pushBack(animFrame->clone());
}
}
Animation *newAnim = Animation::create(newArray, _animation->getDelayPerUnit(), _animation->getLoops());
newAnim->setRestoreOriginalFrame(_animation->getRestoreOriginalFrame());
return Animate::create(newAnim);
}
####知识点补漏 —
-
std::nothrow
:在内存不足时,new (std::nothrow)并不抛出异常,而是将指针置NULLAnimation * animat = new (std::nothrow) Animation; if (!animat) { }
青春是一场大雨,即使感冒了,还盼回头再淋一次......微笑永远是一个人身上最好看的东西...