一、使用示例
auto sp_frame_plist = "animations/grossini.plist";
auto animationConf = "animations/animations-2.plist"
auto aniCache = AnimationCache::getInstance();
auto spFrameCache = SpriteFrameCache::getInstance();
// *********************************************************
// *********** 示例一,挨个创建SpriteFrame/AnimationFrame,加载plist大纹理
// *********************************************************
// 加载SpriteFrame
spFrameCache->addSpriteFramesWithFile(sp_frame_plist);
// 获取指定SpriteFrame序列
Vector<SpriteFrame*> spFrames(15);
for(......) {
spFrames.pushBack(spFrameCache->getSpriteFrameByName(...));
}
// 创建animation
auto aniData = Animation::createWithSpriteFrames(spFrames);
// 缓存animation
AnimationCache::getInstance()->addAnimation( aniData, "dance" );
// 指定帧动画
runAction(Animate::create(aniData));
// *********************************************************
// *********** 示例二,加载Animation配置文件,一步到位
// *********************************************************
aniCache->addAnimationsWithFile( animationConf );
auto aniData1 = animCache->getAnimation("dance_1");
auto aniData2 = animCache->getAnimation("dance_2");
auto aniData3 = animCache->getAnimation("dance_3");
runAction(Sequence::create(
Animate::create(aniData1),
Animate::create(aniData2),
Animate::create(aniData3),
nullptr));
上面涉及的资源文件如下:
- sprite frame plist
- 动画配置文件
- animations-2.plist.txt
- 包含了上面的3个sprite frame plist
二、UML
点击查看【processon】三、AnimationFrame:一个动画帧
1、数据结构
```cpp
// 一个动画帧,包含: // 一个精灵帧,就是一帧的画面 // 帧时延 // 播放到此帧的广播数据 class CC_DLL AnimationFrame : public Ref, public Clonable { protected:
SpriteFrame* _spriteFrame; // 一个动画帧 对应 一个精灵帧,就是一个画面
float _delayUnits; // 这一帧有几个delay unit
// 如果_userInfo != null,则播放到这一帧时,
// 默认eventDispatcher会广播一个EventCustom(AnimationFrameDisplayedNotification)
// 事件的userData就是这个userinfo
ValueMap _userInfo;
};
<a name="2l0jf"></a>
## 2、创建
```cpp
class CC_DLL AnimationFrame : public Ref, public Clonable
{
public:
// spriteFrame: 这一帧的画面
// delayUnits: 这一帧有几个delay unit
// userInfo: 播放到此帧时广播的event custom的userdata
static AnimationFrame* create( SpriteFrame* spriteFrame,
float delayUnits,
const ValueMap& userInfo);
virtual AnimationFrame *clone() const override;
};
3、重要操作
class CC_DLL AnimationFrame : public Ref, public Clonable
{
// 更改这一帧的画面
void setSpriteFrame(SpriteFrame* frame);
SpriteFrame* getSpriteFrame() const;
// 设置这一帧的delay unit数量,决定这一帧的持续时间(时延delaytime)
// delaytime = delayUnits * delayPerUnit
// 在这里设置每一帧的持续时间。
void setDelayUnits(float delayUnits);
float getDelayUnits() const;
// 播放到此帧时广播的event custom的userdata
void setUserInfo(const ValueMap& userInfo);
const ValueMap& getUserInfo() const;
ValueMap& getUserInfo();
}
四、Animation:一个帧动画
1、数据结构
// 包含了一个帧动画的所有数据:我觉得叫做AnimationData更贴切一点。
// 帧序列
// total delay unit
// delay per unit
// duration,播放一次的动画总时长
// loop,动画播放次数
class CC_DLL Animation : public Ref, public Clonable
{
float _totalDelayUnits; // 总共的delay unit数量
float _delayPerUnit; // 一个 delay unit的时长
float _duration; // 动画总时长,totalDelayUnits * delayPerUnit
Vector<AnimationFrame*> _frames; // 动画帧
bool _restoreOriginalFrame; // 动画结束时候是否恢复到第一帧。
unsigned int _loops; // 播放次数
}
2、创建
class CC_DLL Animation : public Ref, public Clonable
{
static Animation* create(void); // 空Animation
// 创建一个“匀速”的帧动画,每一帧一个delay unit。
// arrayOfSpriteFrameNames: 用于创建AnimationFrame
// delayPerUnit: 一个delay unit的时长
// loops: 播放次数
static Animation* createWithSpriteFrames(
const Vector<SpriteFrame*>& arrayOfSpriteFrameNames,
float delayPerUnit = 0.0f,
unsigned int loops = 1);
// 创建一个自定义帧的帧动画,每一帧的delay unit数量可能不同,也就可能是个“变速”帧动画
// arrayOfAnimationFrameNames: 动画帧
// delayPerUnit: 一个delay unit的时长
// loops: 播放次数
static Animation* create(
const Vector<AnimationFrame*>& arrayOfAnimationFrameNames,
float delayPerUnit,
unsigned int loops = 1);
// 拷贝一个完全相同的帧动画。
virtual Animation *clone() const override;
}
3、重要操作
class CC_DLL Animation : public Ref, public Clonable
{
// 末尾添加动画帧
void addSpriteFrame(SpriteFrame *frame);
void addSpriteFrameWithFile(const std::string& filename);
void addSpriteFrameWithTexture(Texture2D* pobTexture, const Rect& rect);
// 更换所有帧,注意会清空并release之前的动画帧。
void setFrames(const Vector<AnimationFrame*>& frames){
_frames = frames; // Vector的拷贝复制
Vector<T>& operator=(const Vector<T>& other) // Vector的拷贝复制逻辑
{
if (this != &other) {
clear(); // 先release每个item,然后并清空vector
_data = other._data; // std::vector的拷贝赋值,拷贝每个item
addRefForAllObjects(); // 每个item都retain一次。
}
return *this;
}
}
// 动画结束,恢复到第一帧
void setRestoreOriginalFrame(bool restoreOriginalFrame);
}
五、Animate:执行帧动画
1、数据结构
// 是一个延时Action
class CC_DLL Animate : public ActionInterval
{
protected:
// 为啥要动态分配_splitTimes?没看懂。
std::vector<float>* _splitTimes; // 播放一次时,每一帧的进度,0.0 ~ 1.0,1.0表示播放到这一帧就播放完了。
int _nextFrame; // next frame index,第一帧index = 0
SpriteFrame* _origFrame; // sprite的sprite frame
int _currFrameIndex; // current frame index
unsigned int _executedLoops; // 已经播放次数
Animation* _animation; // 帧动画数据
EventCustom* _frameDisplayedEvent; // 播放每一帧时候都会广播一个eventcustom,event name是AnimationFrameDisplayedNotification
AnimationFrame::DisplayedEventInfo _frameDisplayedEventInfo; // 这个event的userdata
};
2、重要逻辑
- Animate::initWithAnimation
- 初始化Action,比如action的时长
- Animate::update
- 每一渲染帧的动画更新。 ```cpp
bool Animate::initWithAnimation(Animation* animation) { …… float singleDuration = animation->getDuration(); // 播放一次的时长
// 设置action的总时长,才能计算出每一渲染帧时的执行进度。
if ( ActionInterval::initWithDuration(singleDuration * animation->getLoops() ) )
{
......
float accumUnitsOfTime = 0; // 累计delay unit
// delaytime per unit,这一步没看懂,为什么不能直接animation->getDelayPerUnit();
float newUnitOfTimeValue = singleDuration / animation->getTotalDelayUnits();
// 下面主要是为了计算出每一帧的执行进度(播放一次的)
auto& frames = animation->getFrames();
for (auto& frame : frames)
{
float value = (accumUnitsOfTime * newUnitOfTimeValue) / singleDuration;
accumUnitsOfTime += frame->getDelayUnits();
_splitTimes->push_back(value);
}
return true;
}
return false;
}
void Animate::update(float t)
{
// if t==1, ignore. Animation should finish with t==1
if( t < 1.0f )
{
t *= _animation->getLoops();
// new loop? If so, reset frame counter
unsigned int loopNumber = (unsigned int)t;
if( loopNumber > _executedLoops ) {
_nextFrame = 0;
_executedLoops++;
}
// 整数模除的浮点数版本,
// 整数版:5 mod 3 = 2
// 浮点版:5.1 mod 3 = 2.1
t = fmodf(t, 1.0f); // 得到当前时刻在一个loop中的进度。
// 比如loop = 5,当前进度0.5,得到t=0.5,表示当前在一个循环的中间,这样才好决定显示哪一帧。
}
auto& frames = _animation->getFrames();
SpriteFrame* frameToDisplay = nullptr;
for( int i = _nextFrame; i < frames.size(); i++ )
{
// 第i帧对应的进度,0 ~ 1
float splitTime = _splitTimes->at(i);
// 一直循环到t进度对应的那一帧。
if( splitTime <= t )
{
auto blend = static_cast<Sprite*>(_target)->getBlendFunc();
_currFrameIndex = i;
AnimationFrame* frame = frames.at(_currFrameIndex);
frameToDisplay = frame->getSpriteFrame();
static_cast<Sprite*>(_target)->setSpriteFrame(frameToDisplay);
static_cast<Sprite*>(_target)->setBlendFunc(blend);
// 发送播放第i帧的广播。
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;
}
// Issue 1438. Could be more than one frame per tick, due to low frame rate or frame delta < 1/FPS
else {
break;
}
}
}
<a name="szHY5"></a>
# 六、AnimationCache:帧动画缓存
<a name="gZKji"></a>
## 1、数据结构
```cpp
class CC_DLL AnimationCache : public Ref
{
// key: animation name,见下面的plist配置,
// value: animation
Map<std::string, Animation*> _animations;
static AnimationCache* s_sharedAnimationCache; // 全局单例
};
2、重要操作
class CC_DLL AnimationCache : public Ref
{
public:
// 销毁所有缓存数据
static void destroyInstance();
// animation 缓存在一个map中,name就是key
void addAnimation(Animation *animation, const std::string& name);
void removeAnimation(const std::string& name);
// 注意cache随时可能会销毁animation,因此返回之后要retain,确保使用期间不会被销毁。
Animation* getAnimation(const std::string& name);
// 根据帧动画配置文件,批量加载并缓存帧动画数据。
// 配置文件格式见下面,实际调用的是下面的方法。
void addAnimationsWithFile(const std::string& plist);
// 这个方法设计的很奇怪,还不如不要public,初衷是这样的: plist是帧动画配置文件的路径
// 而帧动画使用到的精灵表规定和这个plist同目录,即配置和数据在一起。
//
// dictionary: 帧动画的配置,文件格式见下面
// plist: 配置文件路径,规定:精灵表和配置文件同目录。
void addAnimationsWithDictionary(const ValueMap& dictionary,const std::string& plist);
};
3、帧动画配置文件
是一个plist文件,内容样板如下:
总的来说,有两个标签:
- anamations:保存所有的animation,以及每个animation下的每一帧animationFrame。
- properties:保存这些帧动画使用到的精灵表、配置文件版本(format) ```xml
<?xml version=”1.0” encoding=”UTF-8”?> <!DOCTYPE plist PUBLIC “-//Apple//DTD PLIST 1.0//EN” “http://www.apple.com/DTDs/PropertyList-1.0.dtd">
```