cocos2d-x-AnimationCache

| 分类 cocos2d-x  | 标签 cocos2d-x 源码学习 

本文为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);
}

####知识点补漏

  1. std::nothrow:在内存不足时,new (std::nothrow)并不抛出异常,而是将指针置NULL

     Animation * animat = new (std::nothrow) Animation;
     if (!animat)
     {
    
     }
    

青春是一场大雨,即使感冒了,还盼回头再淋一次...image...微笑永远是一个人身上最好看的东西...


PREVIOUS     NEXT