cocos2dx骨骼动画Armature源码剖析(三)

网友投稿 219 2023-07-29


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就对应于节点()。

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中的中的b节点,FrameData对应xml中的节点,BoneData和FrameData都有

等属性,BaseDa代表了这些属性。

BoneData

BoneData对应xml中的中的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 displayDataList;

//仿射变换,程序里好像没用这个属性

cocosd::AffineTransform boneDataTransform;

};

BoneData里有displayDataList,用来放这个骨头上的皮肤(就是DisplayData), DisplayData对应xml节点中的>节点,一个BoneData里可以有多个皮肤,换装等功能需要多个皮肤。

FrameData

FrameData对应xml中的节点,就是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是对应节点,里面有这个骨骼的所有骨头,可以看成骨骼动画的骨骼。

class ArmatureData : public cocosd::Ref

{

public:

//添加骨骼信息

void addBoneData(BoneData *boneData);

BoneData *getBoneData(const std::string& boneName);

public:

std::string name;

//多个骨头信息

cocosd::Map boneDataDic;

float dataVersion;

};

AnimationData

AnimationData对应节点,里面有多个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:

//中的name

std::string name;

//所有带帧标签的动画map

cocosd::Map movementDataDic;

//所有带帧标签的动画名

std::vector movementNames;

};

MovementData

MovementData对应xml中>, 其中有所有的带帧信息的骨骼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 movBoneDataDic;

};

MovementBoneData

MovementBoneData对应xml中的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 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& getArmatureDatas() const;

const cocosd::Map& getAnimationDatas() const;

const cocosd::Map& getTextureDatas() const;

protected:

void addRelativeData(const std::string& configFilePath);

RelativeData *getRelativeData(const std::string& configFilePath);

private:

cocosd::Map _armarureDatas;

cocosd::Map _animationDatas;

cocosd::Map _textureDatas;

std::unordered_map _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(isbihttp://naryfilesrc)

{

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->FirstChildElementZhZDJxft(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 _boneDic; cocosd::Vector _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 _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(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(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(object))

{

Node *node = bone->getDisplayRenderNode();

if (nullptr == node)

continue;

switch (bone->getDisplayRenderNodeType())

{

case CS_DISPLAY_SPRITE:

{

Skin *skin = static_cast(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(object))

{

node->visit(renderer, transform, flags);

// CC_NODE_DRAW_SETUP();

}

}

}

再skin->draw(renderer, transform, flags);会用到刚刚更新的_quad,显示出最新的图片信息。

{

Mat mv = Director::getInhttp://stance()->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%就可以了,细节可以需要的时候在仔细理解。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信公众号支付(二)实现统一下单接口
下一篇:cocos2dx骨骼动画Armature源码剖析(二)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~