博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
cocos2dx骨骼动画Armature源码分析(三)
阅读量:6933 次
发布时间:2019-06-27

本文共 22768 字,大约阅读时间需要 75 分钟。

代码目录结构

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

1 class  BaseData : public cocos2d::Ref 2 { 3 public: 4      //Calculate two BaseData's between value(to - from) and set to self 5     virtual void subtract(BaseData *from, BaseData *to, bool limit); 6 public: 7      //位置,xml的x,y 8     float x;                     9     float y;        10     //xml中z         11     int zOrder; 12     //旋转,xml的kX,kY13     float skewX;    14     float skewY;    15     //缩放,xml的cX,cY16     float scaleX;   17     float scaleY;   18      //啥??19     float tweenRotate;     20      //颜色的变化属性 21     bool isUseColorInfo;    22     int a, r, g, b;23 };

作为FrameData和BoneData的基类,提供骨骼的状态信息。从下文可知BoneData对应xml中的<armature<b>>中的b节点,FrameData对应xml中的<f>节点,BoneData和FrameData都有

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

BoneData

BoneData对应xml中的<armature<b>>中的b节点

1 class  BoneData : public BaseData 2 { 3 public: 4     void addDisplayData(DisplayData *displayData); 5     DisplayData *getDisplayData(int index); 6 public: 7     std::string name;           //! the bone's name 8     std::string parentName;     //! the bone parent's name 9     //! save DisplayData informations for the Bone10     cocos2d::Vector
displayDataList; 11 //仿射变换,程序里好像没用这个属性 12 cocos2d::AffineTransform boneDataTransform;13 };

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

FrameData

FrameData对应xml中的<f>节点,就是flash里的关键帧信息。

1 class  FrameData : public BaseData 2 { 3 public: 4     int frameID; 5     //xml中dr,这一帧长度 6     int duration;                7     //不知要他干啥 8     bool isTween;   9     //xml中dI,显示哪个图              10     int displayIndex;11 };

DisplayData

DisplayData是SpriteDisplayData、ArmatureDisplayData、ParticleDisplayData的父类,用来表示展示节点信息。

ArmatureData

ArmatureData是对应<armature>节点,里面有这个骨骼的所有骨头,可以看成骨骼动画的骨骼。

1 class  ArmatureData : public cocos2d::Ref 2 { 3 public: 4      //添加骨骼信息 5     void addBoneData(BoneData *boneData); 6     BoneData *getBoneData(const std::string& boneName); 7 public: 8     std::string name; 9     //多个骨头信息10     cocos2d::Map
boneDataDic;11 float dataVersion;12 };

AnimationData

AnimationData对应<animation>节点,里面有多个MovementData,MovementData(下面介绍)对应xml中的mov,为flash中的一个带帧标签的动画。

1 class  AnimationData : public cocos2d::Ref 2 { 3 public: 4     void addMovement(MovementData *movData); 5     MovementData *getMovement(const std::string& movementName); 6     ssize_t getMovementCount(); 7 public: 8      //
中的name 9 std::string name;10 //所有带帧标签的动画map11 cocos2d::Map
movementDataDic;12 //所有带帧标签的动画名13 std::vector
movementNames;14 };

MovementData

MovementData对应xml中<animation<mov>>, 其中有所有的带帧信息的骨骼MovementBoneData(mov中的b)。

1 class  MovementData : public cocos2d::Ref 2 { 3 public: 4     void addMovementBoneData(MovementBoneData *movBoneData); 5     MovementBoneData *getMovementBoneData(const std::string& boneName); 6 public: 7     std::string name; 8     //xml 中 dr 9     int duration;10     //这怎么有个scale??    11     float scale;    12     //xml中to      13     int durationTo;14     //xml中drTW15     int durationTween;16      //xml中lp17     bool loop;18     //带帧信息的骨骼        19     cocos2d::Map
movBoneDataDic;20 };

MovementBoneData

MovementBoneData对应xml中<mov<b>>的b,里面有frameList,即为关键帧信息。

1 class  MovementBoneData : public cocos2d::Ref 2 { 3     void addFrameData(FrameData *frameData); 4     FrameData *getFrameData(int index); 5 public: 6      //xml中的dl 7     float delay; 8     //xml中的sc               9     float scale;     10     //这个和MovementData中的duration是不是一个??      11     float duration;        12     std::string name;    13      //关键帧信息14     cocos2d::Vector
frameList;15 };

小总结

xml中的各个节点和XXData的对应关系如下表,xml各个字段的意义可以参考文章

再来看产生动画相关的代码

ArmatureDataManager

ArmatureDataManager利用DataReaderHelper解析出armarureDatas、animationDatas和_textureDatas。ArmatureDataManager是个单例,用到动画时会到ArmatureDataManager取得要生成动画的数据。

1 class  ArmatureDataManager : public cocos2d::Ref 2 { 3 public: 4      //单例     5     static ArmatureDataManager *getInstance(); 6     static void destroyInstance(); 7 public: 8     void addArmatureData(const std::string& id, ArmatureData *armatureData, const std::string& configFilePath = ""); 9     ArmatureData *getArmatureData(const std::string& id);10     void removeArmatureData(const std::string& id);11     void addAnimationData(const std::string& id, AnimationData *animationData, const std::string& configFilePath = "");12     AnimationData *getAnimationData(const std::string& id);13     void removeAnimationData(const std::string& id);14     void addTextureData(const std::string& id, TextureData *textureData, const std::string& configFilePath = "");15     TextureData *getTextureData(const std::string& id);16     void removeTextureData(const std::string& id);17     void addArmatureFileInfo(const std::string& configFilePath);18     const cocos2d::Map
& getArmatureDatas() const;19 const cocos2d::Map
& getAnimationDatas() const;20 const cocos2d::Map
& getTextureDatas() const;21 protected:22 void addRelativeData(const std::string& configFilePath);23 RelativeData *getRelativeData(const std::string& configFilePath);24 private:25 cocos2d::Map
_armarureDatas;26 cocos2d::Map
_animationDatas;27 cocos2d::Map
_textureDatas;28 std::unordered_map
_relativeDatas;29 };

主要就是armarureDatas、animationDatas、_textureDatas三个map,那这三个map是怎么产生的呢?当执行

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

后,那三个map变生成了。addArmatureFileInfo代码如下

1 void ArmatureDataManager::addArmatureFileInfo(const std::string& configFilePath)2 {3     addRelativeData(configFilePath);4     _autoLoadSpriteFile = true;5     DataReaderHelper::getInstance()->addDataFromFile(configFilePath);6 }

又调用了DataReaderHelper::getInstance()->addDataFromFile(),可知是DataReaderHelper真正完成了数据的解析。DataReaderHelper类里有一堆decodeXXX()(比如decodeArmature、decodeBone)解析xml的某个节点。看下addDataFromFile这个代码:

1 void DataReaderHelper::addDataFromFile(const std::string& filePath) 2 { 3      //省略一些代码 4      5     DataInfo dataInfo; 6     dataInfo.filename = filePathStr; 7     dataInfo.asyncStruct = nullptr; 8     dataInfo.baseFilePath = basefilePath; 9     if (str == ".xml")10     {11         DataReaderHelper::addDataFromCache(contentStr, &dataInfo);12     }13     else if(str == ".json" || str == ".ExportJson")14     {15         DataReaderHelper::addDataFromJsonCache(contentStr, &dataInfo);16     }17     else if(isbinaryfilesrc)18     {19         DataReaderHelper::addDataFromBinaryCache(contentStr.c_str(),&dataInfo);20     }21 22     CC_SAFE_DELETE_ARRAY(pBytes);23 }

对应不同的文件(xml、json、二进制)解析方式,xml用到是addDataFromCache

1 void DataReaderHelper::addDataFromCache(const std::string& pFileContent, DataInfo *dataInfo) 2 { 3     tinyxml2::XMLDocument document; 4     document.Parse(pFileContent.c_str()); 5  6     tinyxml2::XMLElement *root = document.RootElement(); 7     CCASSERT(root, "XML error  or  XML is empty."); 8  9     root->QueryFloatAttribute(VERSION, &dataInfo->flashToolVersion);10 11 12     /*13     * Begin decode armature data from xml14     */15     tinyxml2::XMLElement *armaturesXML = root->FirstChildElement(ARMATURES);16     tinyxml2::XMLElement *armatureXML = armaturesXML->FirstChildElement(ARMATURE);17     while(armatureXML)18     {19         ArmatureData *armatureData = DataReaderHelper::decodeArmature(armatureXML, dataInfo);20 21         if (dataInfo->asyncStruct)22         {23             _dataReaderHelper->_addDataMutex.lock();24         }25         ArmatureDataManager::getInstance()->addArmatureData(armatureData->name.c_str(), armatureData, dataInfo->filename.c_str());26         armatureData->release();27         if (dataInfo->asyncStruct)28         {29             _dataReaderHelper->_addDataMutex.unlock();30         }31 32         armatureXML = armatureXML->NextSiblingElement(ARMATURE);33     }34 35 36     /*37     * Begin decode animation data from xml38     */39     tinyxml2::XMLElement *animationsXML = root->FirstChildElement(ANIMATIONS);40     tinyxml2::XMLElement *animationXML = animationsXML->FirstChildElement(ANIMATION);41     while(animationXML)42     {43         AnimationData *animationData = DataReaderHelper::decodeAnimation(animationXML, dataInfo);44         if (dataInfo->asyncStruct)45         {46             _dataReaderHelper->_addDataMutex.lock();47         }48         ArmatureDataManager::getInstance()->addAnimationData(animationData->name.c_str(), animationData, dataInfo->filename.c_str());49         animationData->release();50         if (dataInfo->asyncStruct)51         {52             _dataReaderHelper->_addDataMutex.unlock();53         }54         animationXML = animationXML->NextSiblingElement(ANIMATION);55     }56 57 58     /*59     * Begin decode texture data from xml60     */61     tinyxml2::XMLElement *texturesXML = root->FirstChildElement(TEXTURE_ATLAS);62     tinyxml2::XMLElement *textureXML = texturesXML->FirstChildElement(SUB_TEXTURE);63     while(textureXML)64     {65         TextureData *textureData = DataReaderHelper::decodeTexture(textureXML, dataInfo);66 67         if (dataInfo->asyncStruct)68         {69             _dataReaderHelper->_addDataMutex.lock();70         }71         ArmatureDataManager::getInstance()->addTextureData(textureData->name.c_str(), textureData, dataInfo->filename.c_str());72         textureData->release();73         if (dataInfo->asyncStruct)74         {75             _dataReaderHelper->_addDataMutex.unlock();76         }77         textureXML = textureXML->NextSiblingElement(SUB_TEXTURE);78     }79 }

里面有三个while,分别decodeArmature、decodeAnimation、decodeTexture,生成ArmatureData、AnimationData、TextureData之后又ArmatureDataManager::getInstance()->addArmatureData、addAnimationData、addTextureData,加到ArmatureDataManager对应map里。decodeXXX里又会调用各种decodeXX来生成相应的XXXData。

Armature

在载入了xml数据后,调用

1     armature = Armature::create("Dragon");2     armature->getAnimation()->play("walk");3     armature->getAnimation()->setSpeedScale(1);4     armature->setPosition(VisibleRect::center().x, VisibleRect::center().y * 0.3f);5     armature->setScale(0.6f);6     addChild(armature);

便展示了动画,那么这是如何做到的呢?

Armature部分代码如下,ArmatureAnimation控制xml的mov节点,Bone中有Tween,这个Tween对应xml中b(MovementBoneData)

1 class Armature: public cocos2d::Node, public cocos2d::BlendProtocol { 2 protected: 3      //要展示动画的ArmatureData 4     ArmatureData *_armatureData; 5     BatchNode *_batchNode; 6     Bone *_parentBone; 7     float _version; 8     mutable bool _armatureTransformDirty; 9     //所有Bone10     cocos2d::Map
_boneDic; cocos2d::Vector
_topBoneList;11 12 cocos2d::BlendFunc _blendFunc; 13 cocos2d::Vec2 _offsetPoint;14 cocos2d::Vec2 _realAnchorPointInPoints;15 //动画控制器16 ArmatureAnimation *_animation;17 };

Bone

部分代码如下,tweenData为当前Bone的状态,每帧都会更新这个值,并用tweenData确定worldInfo,提供Skin显示信息。tween为骨头的整个动画过程。

1 class Bone: public cocos2d::Node { 2 protected: 3     BoneData *_boneData; 4  5     //! A weak reference to the Armature 6     Armature *_armature; 7  8     //! A weak reference to the child Armature 9     Armature *_childArmature;10 11     DisplayManager *_displayManager;12 13     /*14      *  When Armature play an animation, if there is not a MovementBoneData of this bone in this MovementData, this bone will be hidden.15      *  Set IgnoreMovementBoneData to true, then this bone will also be shown.16      */17     bool _ignoreMovementBoneData;18 19     cocos2d::BlendFunc _blendFunc;20     bool _blendDirty;21 22     Tween *_tween;              //! Calculate tween effect23 24     //! Used for making tween effect in every frame25     FrameData *_tweenData;26 27     Bone *_parentBone;                 //! A weak reference to its parent28     bool _boneTransformDirty;          //! Whether or not transform dirty29 30     //! self Transform, use this to change display's state31     cocos2d::Mat4 _worldTransform;32 33     BaseData *_worldInfo;34     35     //! Armature's parent bone36     Bone *_armatureParentBone;37 38 };

Tween

这个是每个骨头的动画过程,见下面的movementBoneData。tweenData是Bone中tweenData的引用,在这每帧会计算这个tweenData值。

1 class  Tween : public ProcessBase{ 2 protected: 3     //! A weak reference to the current MovementBoneData. The data is in the data pool 4     MovementBoneData *_movementBoneData; 5  6     FrameData *_tweenData;          //! The computational tween frame data, //! A weak reference to the Bone's tweenData 7     FrameData *_from;               //! From frame data, used for calculate between value 8     FrameData *_to;                 //! To frame data, used for calculate between value 9     10     // total diff guan11     FrameData *_between;            //! Between frame data, used for calculate current FrameData(m_pNode) value12 13     Bone *_bone;                    //! A weak reference to the Bone14 15     TweenType _frameTweenEasing;  //! Dedermine which tween effect current frame use16 17     int _betweenDuration;           //! Current key frame will last _betweenDuration frames18     19     // 总共运行了多少帧 guan20     int _totalDuration;21 22     int _fromIndex;                 //! The current frame index in FrameList of MovementBoneData, it's different from m_iFrameIndex23     int _toIndex;                   //! The next frame index in FrameList of MovementBoneData, it's different from m_iFrameIndex24 25     ArmatureAnimation *_animation;26 27     bool _passLastFrame;            //! If current frame index is more than the last frame's index28 };

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。

1 void Armature::update(float dt)2 {3     _animation->update(dt);4     for(const auto &bone : _topBoneList) {5         bone->update(dt);6     }7     _armatureTransformDirty = false;8 }

又调用了animation->update(dt);及遍历调用bone->update(dt);animation->update(dt)如下:

1 void ArmatureAnimation::update(float dt) 2 { 3     ProcessBase::update(dt); 4      5     for (const auto &tween : _tweenList) 6     { 7         tween->update(dt); 8     } 9     //省略一堆代码10 }

又调用了tween->update(dt); 每一个update都会调用updateHandler(ProcessBase中update调用了update里调用updateHandler)

1 void Tween::updateHandler() 2 { 3      //省略一堆代码 4     if (_loopType > ANIMATION_TO_LOOP_BACK) 5     { 6         percent = updateFrameData(percent); 7     } 8  9     if(_frameTweenEasing != ::cocos2d::tweenfunc::TWEEN_EASING_MAX)10     {11         tweenNodeTo(percent);12     }13 }

tweenNodeTo调用了tweenNodeTo,其中的tweenData其实就是Bone的tweenData。根据percent计算了_tweenData的变化量。

1 FrameData *Tween::tweenNodeTo(float percent, FrameData *node) 2 { 3     node = node == nullptr ? _tweenData : node; 4  5     if (!_from->isTween) 6     { 7         percent = 0; 8     } 9 10     node->x = _from->x + percent * _between->x;11     node->y = _from->y + percent * _between->y;12     node->scaleX = _from->scaleX + percent * _between->scaleX;13     node->scaleY = _from->scaleY + percent * _between->scaleY;14     node->skewX = _from->skewX + percent * _between->skewX;15     node->skewY = _from->skewY + percent * _between->skewY;16 17     _bone->setTransformDirty(true);18 19     if (node && _between->isUseColorInfo)20     {21         tweenColorTo(percent, node);22     }23 24     return node;25 }

转了一大圈终于在每帧更新了Bone中的tweenData,最后看Bone的update,其根据tweenData计算了worldInfo、worldTransform。而且updateDisplay更新skin的信息,staticcast<skin*>(display)->updateArmatureTransform();再transform = TransformConcat(_bone->getNodeToArmatureTransform(), _skinTransform);

1 void Bone::update(float delta) 2 { 3     if (_parentBone) 4         _boneTransformDirty = _boneTransformDirty || _parentBone->isTransformDirty(); 5  6     if (_armatureParentBone && !_boneTransformDirty) 7     { 8         _boneTransformDirty = _armatureParentBone->isTransformDirty(); 9     }10 11     if (_boneTransformDirty)12     {13         if (_dataVersion >= VERSION_COMBINED)14         {15             TransformHelp::nodeConcat(*_tweenData, *_boneData);16             _tweenData->scaleX -= 1;17             _tweenData->scaleY -= 1;18         }19 20         _worldInfo->copy(_tweenData);21 22         _worldInfo->x = _tweenData->x + _position.x;23         _worldInfo->y = _tweenData->y + _position.y;24         _worldInfo->scaleX = _tweenData->scaleX * _scaleX;25         _worldInfo->scaleY = _tweenData->scaleY * _scaleY;26         _worldInfo->skewX = _tweenData->skewX + _skewX + _rotationZ_X;27         _worldInfo->skewY = _tweenData->skewY + _skewY - _rotationZ_Y;28 29         if(_parentBone)30         {31             applyParentTransform(_parentBone);32         }33         else34         {35             if (_armatureParentBone)36             {37                 applyParentTransform(_armatureParentBone);38             }39         }40 41         TransformHelp::nodeToMatrix(*_worldInfo, _worldTransform);42 43         if (_armatureParentBone)44         {45             _worldTransform = TransformConcat(_worldTransform, _armature->getNodeToParentTransform());46         }47     }48 49     DisplayFactory::updateDisplay(this, delta, _boneTransformDirty || _armature->getArmatureTransformDirty());50 51     for(const auto &obj: _children) {52         Bone *childBone = static_cast
(obj);53 childBone->update(delta);54 }55 56 _boneTransformDirty = false;

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

Armature诗歌node,加入父节点后会调用其draw函数,遍历draw了bone的显示元素。

1 void Armature::draw(cocos2d::Renderer *renderer, const Mat4 &transform, uint32_t flags) 2 { 3     if (_parentBone == nullptr && _batchNode == nullptr) 4     { 5 //        CC_NODE_DRAW_SETUP(); 6     } 7  8  9     for (auto& object : _children)10     {11         if (Bone *bone = dynamic_cast
(object))12 {13 Node *node = bone->getDisplayRenderNode();14 15 if (nullptr == node)16 continue;17 18 switch (bone->getDisplayRenderNodeType())19 {20 case CS_DISPLAY_SPRITE:21 {22 Skin *skin = static_cast
(node);23 skin->updateTransform();24 25 BlendFunc func = bone->getBlendFunc();26 27 if (func.src != _blendFunc.src || func.dst != _blendFunc.dst)28 {29 skin->setBlendFunc(bone->getBlendFunc());30 }31 else32 {33 skin->setBlendFunc(_blendFunc);34 }35 skin->draw(renderer, transform, flags);36 }37 break;38 case CS_DISPLAY_ARMATURE:39 {40 node->draw(renderer, transform, flags);41 }42 break;43 default:44 {45 node->visit(renderer, transform, flags);46 // CC_NODE_DRAW_SETUP();47 }48 break;49 }50 }51 else if(Node *node = dynamic_cast
(object))52 {53 node->visit(renderer, transform, flags);54 // CC_NODE_DRAW_SETUP();55 }56 }57 }

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

1 {2     Mat4 mv = Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);3 4     //TODO implement z order5     _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, mv);6     renderer->addCommand(&_quadCommand);7 }

 

至此,大家对cocos2dx里的骨骼动画应该有了全面的认识,三篇文章介绍的比较粗糙,其实有些细节内容我也没看懂,不过不要在意这些细节,没有实际的改动需求的话,懂80%就可以了,细节可以需要的时候在仔细理解。

转载于:https://www.cnblogs.com/BigFeng/p/4787822.html

你可能感兴趣的文章
mongodb优化
查看>>
resin配置端口和虚拟目录
查看>>
我的友情链接
查看>>
Windows常用的快捷方式
查看>>
CentOS6.4安装Oralce11.2问题总结
查看>>
mysql分区技术
查看>>
跳出当前for循环
查看>>
IDEA java开发学习笔记
查看>>
欢迎大家关注我的微信公账号
查看>>
git学习心得总结
查看>>
猫猫学IOS(三十二)UI之Quartz2D矩阵操作和图片剪切
查看>>
ABBYY FineReader利用模式提高OCR质量
查看>>
代码的编写之惨烈的教训一
查看>>
Git之提交项目到远程github
查看>>
python一中实现组合的方式
查看>>
防火墙技术 配置基于上下文的访问控制
查看>>
简单字符串匹配方法
查看>>
linux安装及管理程序
查看>>
salt源码安装
查看>>
王高利:Kvm虚拟化(3)__nat方式上网
查看>>