首页 > 编程 > JavaScript > 正文

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

2019-11-20 11:35:39
字体:
来源:转载
供稿:网友

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%就可以了,细节可以需要的时候在仔细理解。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表