cocos2dx骨骼動畫Armature原始碼剖析(三)

cocos2dx骨骼動畫Armature原始碼剖析(三)

cocos2dx裡骨骼動畫程式碼在cocos -> editor-support -> cocostudio資料夾中,win下通過篩選器,檔案結構如下。(mac下沒有分,是整個一坨)


armature(目錄):
animation(目錄):動畫控制相關。
CCProcessBase(檔案):
ProcessBase(類):CCTween和ArmatureAnimation的基類。
CCTWeen(檔案):
Tween(類):控制flash裡一個layer的動畫。
CCArmatureAnimation(檔案):
ArmatureAnimation(類):控制整個動畫,內有多個Tween。
datas(目錄):xml或json轉成c  中直接用的資料結構。
CCDatas(檔案):
BaseData(類):BoneData、FrameData的基類,包含大小位置顏色等資訊。
DisplayData(類): SpriteDisplayData、ArmatureDisplayData、ParticleDisplayData的基類。
SpriteDisplayData(類):骨骼中的顯示資料。
ArmatureDisplayData(類):
ParticleDisplayData(類):
BoneData(類):單個骨骼資料,flash中一個layer是一個骨骼。
ArmatureData(類):骨骼資料,整個骨骼結構資料。
FrameData(類):關鍵幀資料。
MovementBoneData(類):帶有關鍵幀的骨骼資料。
MovementData(類):一個完整動畫資料。
AnimationData(類):組動畫資料,包含多個MovementData。
ContourData(類):
TextureData(類):顯示圖片資料。
utils(目錄):
CCArmatureDataManager(檔案):
RelativeData(類):
ArmatureDataManager(類):管理ArmatureData、AnimationData、TextureData。
CCArmatureDefine(檔案):
CCDataReaderHelper(檔案):
_AsyncStruct(類):
_DataInfo(類):
DataReaderHelper(類):這正解析xml或json的類。
CCSpriteFrameCacheHelper(檔案):
SpriteFrameCacheHelper(類):
CCTransformHelp(檔案):
TransformHelp(類):矩陣運算。
CCUtilMath(檔案):
CCArmature(檔案):
Armature(類):控制整個骨骼動畫,內有ArmatureAnimation和ArmatureData。
CCBone(檔案):
Bone(類):骨骼控制類
display(目錄):顯示的圖片管理。
CCBatchNode(檔案):
BatchNode(類):
CCDecorativeDisplay(檔案):
DecorativeDisplay(類):
CCDisplayFactory(檔案):
DisplayFactory(類):
CCDisplayManager(檔案):
DisplayManager(類):
CCSkin(檔案):
Skin(類):
physics(目錄):物理引擎相關,不分析。
ColliderFilter(檔案):
ColliderFilter(類):
ColliderBody(類):
ColliderDetecotor(類)

資料相關原始碼

從底層到高層分析一個類一個類分析

再來看下資料相關的UML,總體來說,就是ArmatureDataManager依賴DataReaderHelper把flash匯出的xml檔案解析成程式直接用的XXData,XXData對應於xml的某個節點,比如FrameData就對應於<f>節點(<animaton><mov><b><f>)。

 

BaseData

BaseData:用來表示骨骼或幀的位置、旋轉、顏色、縮放。


BaseData.h
class BaseData : public cocosd::Ref
{
public:
//Calculate two BaseData's between value(to - from) and set to self
virtual void subtract(BaseData *from, BaseData *to, bool limit);
public:
//位置,xml的x,y
float x;     
float y;  
//xml中z   
int zOrder; 
//旋轉,xml的kX,kY
float skewX; 
float skewY; 
//縮放,xml的cX,cY
float scaleX; 
float scaleY; 
//啥??
float tweenRotate;  
//顏色的變化屬性 
bool isUseColorInfo; 
int a, r, g, b;
};

作為FrameData和BoneData的基類,提供骨骼的狀態資訊。從下文可知BoneData對應xml中的<armature<b>>中的b節點,FrameData對應xml中的<f>節點,BoneData和FrameData都有

<x,y,kX,kY,cX,cY,pX,pY,z>等屬性,BaseDa代表了這些屬性。

BoneData

BoneData對應xml中的<armature<b>>中的b節點


class BoneData : public BaseData
{
public:
void addDisplayData(DisplayData *displayData);
DisplayData *getDisplayData(int index);
public:
std::string name;   //! the bone's name
std::string parentName;  //! the bone parent's name
//! save DisplayData informations for the Bone
cocosd::Vector<DisplayData*> displayDataList; 
//仿射變換,程式裡好像沒用這個屬性 
cocosd::AffineTransform boneDataTransform;
};

BoneData裡有displayDataList,用來放這個骨頭上的面板(就是DisplayData), DisplayData對應xml節點中的<b<d>>節點,一個BoneData裡可以有多個面板,換裝等功能需要多個面板。

FrameData

FrameData對應xml中的<f>節點,就是flash裡的關鍵幀資訊。


class FrameData : public BaseData
{
public:
int frameID;
//xml中dr,這一幀長度
int duration;    
//不知要他幹啥
bool isTween; 
//xml中dI,顯示哪個圖    
int displayIndex;
};

DisplayData

DisplayData是SpriteDisplayData、ArmatureDisplayData、ParticleDisplayData的父類,用來表示展示節點資訊。

ArmatureData

ArmatureData是對應<armature>節點,裡面有這個骨骼的所有骨頭,可以看成骨骼動畫的骨骼。


class ArmatureData : public cocosd::Ref
{
public:
//新增骨骼資訊
void addBoneData(BoneData *boneData);
BoneData *getBoneData(const std::string& boneName);
public:
std::string name;
//多個骨頭資訊
cocosd::Map<std::string, BoneData*> boneDataDic;
float dataVersion;
};

AnimationData

AnimationData對應<animation>節點,裡面有多個MovementData,MovementData(下面介紹)對應xml中的mov,為flash中的一個帶幀標籤的動畫。


class AnimationData : public cocosd::Ref
{
public:
void addMovement(MovementData *movData);
MovementData *getMovement(const std::string& movementName);
ssize_t getMovementCount();
public:
//<animation name="Dragon">中的name
std::string name;
//所有帶幀標籤的動畫map
cocosd::Map<std::string, MovementData*> movementDataDic;
//所有帶幀標籤的動畫名
std::vector<std::string> movementNames;
};

MovementData

MovementData對應xml中<animation<mov>>, 其中有所有的帶幀資訊的骨骼MovementBoneData(mov中的b)。


class MovementData : public cocosd::Ref
{
public:
void addMovementBoneData(MovementBoneData *movBoneData);
MovementBoneData *getMovementBoneData(const std::string& boneName);
public:
std::string name;
//xml 中 dr
int duration;
//這怎麼有個scale?? 
float scale; 
//xml中to  
int durationTo;
//xml中drTW
int durationTween;
//xml中lp
bool loop;
//帶幀資訊的骨骼  
cocosd::Map<std::string, MovementBoneData*> movBoneDataDic;
};

MovementBoneData

MovementBoneData對應xml中<mov<b>>的b,裡面有frameList,即為關鍵幀資訊。


class MovementBoneData : public cocosd::Ref
{
void addFrameData(FrameData *frameData);
FrameData *getFrameData(int index);
public:
//xml中的dl
float delay;
//xml中的sc    
float scale;  
//這個和MovementData中的duration是不是一個??  
float duration;  
std::string name; 
//關鍵幀資訊
cocosd::Vector<FrameData*> frameList;
};

小總結

xml中的各個節點和XXData的對應關係如下表,xml各個欄位的意義可以參考上篇文章

 

再來看產生動畫相關的程式碼

 

ArmatureDataManager

ArmatureDataManager利用DataReaderHelper解析出armarureDatas、animationDatas和_textureDatas。

ArmatureDataManager是個單例,用到動畫時會到ArmatureDataManager取得要生成動畫的資料。


class ArmatureDataManager : public cocosd::Ref
{
public:
//單例 
static ArmatureDataManager *getInstance();
static void destroyInstance();
public:
void addArmatureData(const std::string& id, ArmatureData *armatureData, const std::string& configFilePath = "");
ArmatureData *getArmatureData(const std::string& id);
void removeArmatureData(const std::string& id);
void addAnimationData(const std::string& id, AnimationData *animationData, const std::string& configFilePath = "");
AnimationData *getAnimationData(const std::string& id);
void removeAnimationData(const std::string& id);
void addTextureData(const std::string& id, TextureData *textureData, const std::string& configFilePath = "");
TextureData *getTextureData(const std::string& id);
void removeTextureData(const std::string& id);
void addArmatureFileInfo(const std::string& configFilePath);
const cocosd::Map<std::string, ArmatureData*>&  getArmatureDatas() const;
const cocosd::Map<std::string, AnimationData*>& getAnimationDatas() const;
const cocosd::Map<std::string, TextureData*>&  getTextureDatas() const;
protected:
void addRelativeData(const std::string& configFilePath);
RelativeData *getRelativeData(const std::string& configFilePath);
private:
cocosd::Map<std::string, ArmatureData*> _armarureDatas;
cocosd::Map<std::string, AnimationData*> _animationDatas;
cocosd::Map<std::string, TextureData*> _textureDatas;
std::unordered_map<std::string, RelativeData> _relativeDatas;
};

主要就是armarureDatas、animationDatas、_textureDatas三個map,那這三個map是怎麼產生的呢?當執行


ArmatureDataManager::getInstance()->addArmatureFileInfo(“dragon.xml”);

後,那三個map變生成了。addArmatureFileInfo程式碼如下


void ArmatureDataManager::addArmatureFileInfo(const std::string& configFilePath)
{
addRelativeData(configFilePath);
_autoLoadSpriteFile = true;
DataReaderHelper::getInstance()->addDataFromFile(configFilePath);
}

又呼叫了DataReaderHelper::getInstance()->addDataFromFile(),可知是DataReaderHelper真正完成了資料的解析。

DataReaderHelper類裡有一堆decodeXXX()(比如decodeArmature、decodeBone)解析xml的某個節點。看下

addDataFromFile這個程式碼:


void DataReaderHelper::addDataFromFile(const std::string& filePath)
{
//省略一些程式碼
DataInfo dataInfo;
dataInfo.filename = filePathStr;
dataInfo.asyncStruct = nullptr;
dataInfo.baseFilePath = basefilePath;
if (str == ".xml")
{
DataReaderHelper::addDataFromCache(contentStr, &dataInfo);
}
else if(str == ".json" || str == ".ExportJson")
{
DataReaderHelper::addDataFromJsonCache(contentStr, &dataInfo);
}
else if(isbinaryfilesrc)
{
DataReaderHelper::addDataFromBinaryCache(contentStr.c_str(),&dataInfo);
}
CC_SAFE_DELETE_ARRAY(pBytes);
}

對應不同的檔案(xml、json、二進位制)解析方式,xml用到是addDataFromCache


void DataReaderHelper::addDataFromCache(const std::string& pFileContent, DataInfo *dataInfo)
{
tinyxml::XMLDocument document;
document.Parse(pFileContent.c_str());
tinyxml::XMLElement *root = document.RootElement();
CCASSERT(root, "XML error or XML is empty.");
root->QueryFloatAttribute(VERSION, &dataInfo->flashToolVersion);
/*
* Begin decode armature data from xml
*/
tinyxml::XMLElement *armaturesXML = root->FirstChildElement(ARMATURES);
tinyxml::XMLElement *armatureXML = armaturesXML->FirstChildElement(ARMATURE);
while(armatureXML)
{
ArmatureData *armatureData = DataReaderHelper::decodeArmature(armatureXML, dataInfo);
if (dataInfo->asyncStruct)
{
_dataReaderHelper->_addDataMutex.lock();
}
ArmatureDataManager::getInstance()->addArmatureData(armatureData->name.c_str(), armatureData, dataInfo->filename.c_str());
armatureData->release();
if (dataInfo->asyncStruct)
{
_dataReaderHelper->_addDataMutex.unlock();
}
armatureXML = armatureXML->NextSiblingElement(ARMATURE);
}
/*
* Begin decode animation data from xml
*/
tinyxml::XMLElement *animationsXML = root->FirstChildElement(ANIMATIONS);
tinyxml::XMLElement *animationXML = animationsXML->FirstChildElement(ANIMATION);
while(animationXML)
{
AnimationData *animationData = DataReaderHelper::decodeAnimation(animationXML, dataInfo);
if (dataInfo->asyncStruct)
{
_dataReaderHelper->_addDataMutex.lock();
}
ArmatureDataManager::getInstance()->addAnimationData(animationData->name.c_str(), animationData, dataInfo->filename.c_str());
animationData->release();
if (dataInfo->asyncStruct)
{
_dataReaderHelper->_addDataMutex.unlock();
}
animationXML = animationXML->NextSiblingElement(ANIMATION);
}
/*
* Begin decode texture data from xml
*/
tinyxml::XMLElement *texturesXML = root->FirstChildElement(TEXTURE_ATLAS);
tinyxml::XMLElement *textureXML = texturesXML->FirstChildElement(SUB_TEXTURE);
while(textureXML)
{
TextureData *textureData = DataReaderHelper::decodeTexture(textureXML, dataInfo);
if (dataInfo->asyncStruct)
{
_dataReaderHelper->_addDataMutex.lock();
}
ArmatureDataManager::getInstance()->addTextureData(textureData->name.c_str(), textureData, dataInfo->filename.c_str());
textureData->release();
if (dataInfo->asyncStruct)
{
_dataReaderHelper->_addDataMutex.unlock();
}
textureXML = textureXML->NextSiblingElement(SUB_TEXTURE);
}
}

裡面有三個while,分別decodeArmature、decodeAnimation、decodeTexture,生成ArmatureData、AnimationData、TextureData之後又ArmatureDataManager::getInstance()->addArmatureData、addAnimationData、addTextureData,加到ArmatureDataManager對應map裡。decodeXXX裡又會呼叫各種decodeXX來生成相應的XXXData。

Armature

在載入了xml資料後,呼叫


armature = Armature::create("Dragon");
armature->getAnimation()->play("walk");
armature->getAnimation()->setSpeedScale();
armature->setPosition(VisibleRect::center().x, VisibleRect::center().y * .f);
armature->setScale(.f);
addChild(armature);

便展示了動畫,那麼這是如何做到的呢?

Armature部分程式碼如下,ArmatureAnimation控制xml的mov節點,Bone中有Tween,這個Tween對應xml中b(MovementBoneData)


class Armature: public cocosd::Node, public cocosd::BlendProtocol {
protected:
//要展示動畫的ArmatureData
ArmatureData *_armatureData;
BatchNode *_batchNode;
Bone *_parentBone;
float _version;
mutable bool _armatureTransformDirty;
//所有Bone
cocosd::Map<std::string, Bone*> _boneDic;       cocosd::Vector<Bone*> _topBoneList;
cocosd::BlendFunc _blendFunc;     
cocosd::Vec _offsetPoint;
cocosd::Vec _realAnchorPointInPoints;
//動畫控制器
ArmatureAnimation *_animation;
};

Bone

部分程式碼如下,tweenData為當前Bone的狀態,每幀都會更新這個值,並用tweenData確定worldInfo,提供Skin顯示資訊。tween為骨頭的整個動畫過程。


class Bone: public cocosd::Node {
protected:
BoneData *_boneData;
//! A weak reference to the Armature
Armature *_armature;
//! A weak reference to the child Armature
Armature *_childArmature;
DisplayManager *_displayManager;
/*
* When Armature play an animation, if there is not a MovementBoneData of this bone in this MovementData, this bone will be hidden.
* Set IgnoreMovementBoneData to true, then this bone will also be shown.
*/
bool _ignoreMovementBoneData;
cocosd::BlendFunc _blendFunc;
bool _blendDirty;
Tween *_tween;    //! Calculate tween effect
//! Used for making tween effect in every frame
FrameData *_tweenData;
Bone *_parentBone;     //! A weak reference to its parent
bool _boneTransformDirty;   //! Whether or not transform dirty
//! self Transform, use this to change display's state
cocosd::Mat _worldTransform;
BaseData *_worldInfo;
//! Armature's parent bone
Bone *_armatureParentBone;
};

Tween

這個是每個骨頭的動畫過程,見下面的movementBoneData。tweenData是Bone中tweenData的引用,在這每幀會計算這個tweenData值。


class Tween : public ProcessBase{
protected:
//! A weak reference to the current MovementBoneData. The data is in the data pool
MovementBoneData *_movementBoneData;
FrameData *_tweenData;   //! The computational tween frame data, //! A weak reference to the Bone's tweenData
FrameData *_from;    //! From frame data, used for calculate between value
FrameData *_to;     //! To frame data, used for calculate between value
// total diff guan
FrameData *_between;   //! Between frame data, used for calculate current FrameData(m_pNode) value
Bone *_bone;     //! A weak reference to the Bone
TweenType _frameTweenEasing; //! Dedermine which tween effect current frame use
int _betweenDuration;   //! Current key frame will last _betweenDuration frames
// 總共執行了多少幀 guan
int _totalDuration;
int _fromIndex;     //! The current frame index in FrameList of MovementBoneData, it's different from m_iFrameIndex
int _toIndex;     //! The next frame index in FrameList of MovementBoneData, it's different from m_iFrameIndex
ArmatureAnimation *_animation;
bool _passLastFrame;   //! If current frame index is more than the last frame's index
};

ArmatureAnimation

控制動畫的播放,看到_tweenList,所有骨頭的集合就是動畫了。


class ArmatureAnimation : public ProcessBase {
protected:
//! AnimationData save all MovementDatas this animation used.
AnimationData *_animationData;
MovementData *_movementData;    //! MovementData save all MovementFrameDatas this animation used.
Armature *_armature;      //! A weak reference of armature
std::string _movementID;    //! Current movment's name
int _toIndex;        //! The frame index in MovementData->m_pMovFrameDataArr, it's different from m_iFrameIndex.
cocos2d::Vector<Tween*> _tweenList;
}

如何做到每幀更新骨頭的資訊?

addChild(armature)後,Armaure中的onEnter(node進入舞臺就會呼叫,比如addchild),onEnter調scheduleUpdate調scheduleUpdateWithPriority調_scheduler->scheduleUpdate。這樣就每幀呼叫armature的update。


void Armature::update(float dt)
{
_animation->update(dt);
for(const auto &bone : _topBoneList) {
bone->update(dt);
}
_armatureTransformDirty = false;
}

又呼叫了animation->update(dt);及遍歷呼叫bone->update(dt);animation->update(dt)如下:


void ArmatureAnimation::update(float dt)
{
ProcessBase::update(dt);
for (const auto &tween : _tweenList)
{
tween->update(dt);
}
//省略一堆程式碼
}

又呼叫了tween->update(dt); 每一個update都會呼叫updateHandler(ProcessBase中update呼叫了update裡呼叫updateHandler)


void Tween::updateHandler()
{
//省略一堆程式碼
if (_loopType > ANIMATION_TO_LOOP_BACK)
{
percent = updateFrameData(percent);
}
if(_frameTweenEasing != ::cocosd::tweenfunc::TWEEN_EASING_MAX)
{
tweenNodeTo(percent);
}
}

tweenNodeTo呼叫了tweenNodeTo,其中的tweenData其實就是Bone的tweenData。根據percent計算了_tweenData的變化量。


FrameData *Tween::tweenNodeTo(float percent, FrameData *node)
{
node = node == nullptr ? _tweenData : node;
if (!_from->isTween)
{
percent = ;
}
node->x = _from->x   percent * _between->x;
node->y = _from->y   percent * _between->y;
node->scaleX = _from->scaleX   percent * _between->scaleX;
node->scaleY = _from->scaleY   percent * _between->scaleY;
node->skewX = _from->skewX   percent * _between->skewX;
node->skewY = _from->skewY   percent * _between->skewY;
_bone->setTransformDirty(true);
if (node && _between->isUseColorInfo)
{
tweenColorTo(percent, node);
}
return node;
}

轉了一大圈終於在每幀更新了Bone中的tweenData,最後看Bone的update,其根據tweenData計算了worldInfo、worldTransform。而且updateDisplay更新skin的資訊,staticcast<skin*>(display)->updateArmatureTransform();再transform = TransformConcat(_bone->getNodeToArmatureTransform(), _skinTransform);


void Bone::update(float delta)
{
if (_parentBone)
_boneTransformDirty = _boneTransformDirty || _parentBone->isTransformDirty();
if (_armatureParentBone && !_boneTransformDirty)
{
_boneTransformDirty = _armatureParentBone->isTransformDirty();
}
if (_boneTransformDirty)
{
if (_dataVersion >= VERSION_COMBINED)
{
TransformHelp::nodeConcat(*_tweenData, *_boneData);
_tweenData->scaleX -= ;
_tweenData->scaleY -= ;
}
_worldInfo->copy(_tweenData);
_worldInfo->x = _tweenData->x   _position.x;
_worldInfo->y = _tweenData->y   _position.y;
_worldInfo->scaleX = _tweenData->scaleX * _scaleX;
_worldInfo->scaleY = _tweenData->scaleY * _scaleY;
_worldInfo->skewX = _tweenData->skewX   _skewX   _rotationZ_X;
_worldInfo->skewY = _tweenData->skewY   _skewY - _rotationZ_Y;
if(_parentBone)
{
applyParentTransform(_parentBone);
}
else
{
if (_armatureParentBone)
{
applyParentTransform(_armatureParentBone);
}
}
TransformHelp::nodeToMatrix(*_worldInfo, _worldTransform);
if (_armatureParentBone)
{
_worldTransform = TransformConcat(_worldTransform, _armature->getNodeToParentTransform());
}
}
DisplayFactory::updateDisplay(this, delta, _boneTransformDirty || _armature->getArmatureTransformDirty());
for(const auto &obj: _children) {
Bone *childBone = static_cast<Bone*>(obj);
childBone->update(delta);
}
_boneTransformDirty = false;

如何展示(draw)出圖片(skin)

Armature詩歌node,加入父節點後會呼叫其draw函式,遍歷draw了bone的顯示元素。


void Armature::draw(cocosd::Renderer *renderer, const Mat &transform, uint_t flags)
{
if (_parentBone == nullptr && _batchNode == nullptr)
{
//  CC_NODE_DRAW_SETUP();
}
for (auto& object : _children)
{
if (Bone *bone = dynamic_cast<Bone *>(object))
{
Node *node = bone->getDisplayRenderNode();
if (nullptr == node)
continue;
switch (bone->getDisplayRenderNodeType())
{
case CS_DISPLAY_SPRITE:
{
Skin *skin = static_cast<Skin *>(node);
skin->updateTransform();
BlendFunc func = bone->getBlendFunc();
if (func.src != _blendFunc.src || func.dst != _blendFunc.dst)
{
skin->setBlendFunc(bone->getBlendFunc());
}
else
{
skin->setBlendFunc(_blendFunc);
}
skin->draw(renderer, transform, flags);
}
break;
case CS_DISPLAY_ARMATURE:
{
node->draw(renderer, transform, flags);
}
break;
default:
{
node->visit(renderer, transform, flags);
//    CC_NODE_DRAW_SETUP();
}
break;
}
}
else if(Node *node = dynamic_cast<Node *>(object))
{
node->visit(renderer, transform, flags);
//   CC_NODE_DRAW_SETUP();
}
}
}

再skin->draw(renderer, transform, flags);會用到剛剛更新的_quad,顯示出最新的圖片資訊。


{
Mat mv = Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
//TODO implement z order
_quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, , mv);
renderer->addCommand(&_quadCommand);
}

至此,大家對cocos2dx裡的骨骼動畫應該有了全面的認識,三篇文章介紹的比較粗糙,其實有些細節內容我也沒看懂,不過不要在意這些細節,沒有實際的改動需求的話,懂80%就可以了,細節可以需要的時候在仔細理解。

您可能感興趣的文章:

剖析iOS開發中Cocos2d-x的記憶體管理相關操作iOS開發中使用cocos2d新增觸控事件的方法cocos2dx骨骼動畫Armature原始碼剖析(二)cocos2dx骨骼動畫Armature原始碼剖析(一)Cocos2d-x 3.x入門教程(二):Node節點類Cocos2d-x 3.x入門教程(一):基礎概念Cocos2d-x中呼叫Lua及HelloWorld.lua原始碼分解Cocos2d-x中CCEditBox文字輸入框的使用例項Cocos2d-x中使用CCScrollView來實現關卡選擇例項Cocos2d-x中實現彈出對話方塊示例Cocos2d-x觸控事件例項Cocos2d-x人物動作類例項詳解iOS遊戲開發中Cocos2D的座標位置關係