当前有两种模型动画的方式:顶点动画和骨骼动画。顶点动画中,每帧动画其实就是模型特定姿态的一个“快照”。通过在帧之间插值的方法,引擎可以得到平滑的动画效果。在骨骼动画中,模型具有互相连接的“骨骼”组成的骨架结构,通过改变骨骼的朝向和位置来为模型生成动画。
骨骼动画比顶点动画要求更高的处理器性能,但同时它也具有更多的优点,骨骼动画可以更容易、更快捷地创建。不同的骨骼动画可以被结合到一起——比如,模型可以转动头部、射击并且同时也在走路。一些引擎可以实时操纵单个骨骼,这样就可以和环境更加准确地进行交互——模型可以俯身并向某个方向观察或射击,或者从地上的某个地方捡起一个东西。多数引擎支持顶点动画,但不是所有的引擎都支持骨骼动画。
一些引擎包含面部动画系统,这种系统使用通过音位(phoneme)和情绪修改面部骨骼集合来表达面部表情和嘴部动作。
有关cocoStdio制作骨骼动画,可以参考下用户手册和Cocos2d-x 3.0开发(六)使用cocoStudio创建一个骨骼动画。本篇博文只是讲解怎么在cocos2d-x使用骨骼动画的。
角色类:Player.h
#ifndef __CowboyScene__Player__ #define __CowboyScene__Player__ #include <iostream> #include "cocos2d.h" #include "cocos-ext.h" USING_NS_CC; USING_NS_CC_EXT; using namespace gui; #define WALK_SPEED 1 #define WALK_LEFT 1 #define WALK_RIGHT -1 enum PlayerState { IDLE = 0, //默认 WALK, //行走 SHOOT, //射击 GRENADE //子弹 }; class Player:public CCObject { public: Player(CCNode* node); void update(float dt); //更新角色状态 void updateAnimation(); //状态判断及播放动画 void updateMovement(); //行走状态 void play(std::string animName); //播放动画 bool isLockState(); //锁定角色状态 inline void setState(PlayerState state) {newState = state;} //设置角色状态 void setDirection(int newDirection); //设置方向 private: CCNode* playerNode; CCArmatureAnimation* animation; //动画变量 PlayerState currentState; //当前状态 PlayerState newState; //更换状态 int direction; //方向 bool lockState; //锁定状态 void onAnimationEvent(CCArmature *pArmature, MovementEventType eventType, const char *animationID); //角色射击状态 }; #endif /* defined(__CowboyScene__Player__) */角色类:Player.cpp
#include "Player.h" Player::Player(CCNode* playerNode):CCObject() { CCComRender *pRender = static_cast<CCComRender*>(playerNode->getChildByTag(10004)->getComponent("CCArmature")); this->playerNode = playerNode->getChildByTag(10004); CCArmature* animationNode = static_cast<CCArmature*>(pRender->getNode()); this->animation = animationNode->getAnimation(); this->animation->setMovementEventCallFunc(this, movementEvent_selector(Player::onAnimationEvent)); currentState = IDLE; newState = IDLE; lockState = false; } //设置方向 void Player::setDirection(int newDirection) { direction = newDirection; playerNode->setScaleX(direction * fabs(playerNode->getScaleX())); } void Player::update(float dt) { if (currentState == newState || isLockState()) { updateMovement(); } else { currentState = newState; updateAnimation(); } } void Player::updateMovement() { CCPoint oldPos = playerNode->getPosition(); if (currentState == WALK) { playerNode->setPosition(oldPos.x + -direction * WALK_SPEED,oldPos.y); } } void Player::updateAnimation() { switch (currentState) { case IDLE: animation->play("stand"); break; case SHOOT: animation->play("stand_fire"); break; case WALK: animation->play("walk"); break; case GRENADE: animation->play("grenade"); lockState = true; break; default: break; } } bool Player::isLockState() { return lockState; } void Player::onAnimationEvent(cocos2d::extension::CCArmature *pArmature, cocos2d::extension::MovementEventType eventType, const char *animationID) { if (eventType == LOOP_COMPLETE) { if (strcmp(animationID, "grenade") == 0) { lockState = false; newState = IDLE; } } }
导入骨骼动画及使用:
HelloWorldScene.cpp
bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !CCLayer::init() ) { return false; } CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize(); CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin(); ///////////////////////////// // 2. add a menu item with "X" image, which is clicked to quit the program // you may modify it. // add a "close" icon to exit the progress. it's an autorelease object CCMenuItemImage *pCloseItem = CCMenuItemImage::create( "CloseNormal.png", "CloseSelected.png", this, menu_selector(HelloWorld::menuCloseCallback)); pCloseItem->setPosition(ccp(origin.x + visibleSize.width - pCloseItem->getContentSize().width/2 , origin.y + pCloseItem->getContentSize().height/2)); // create menu, it's an autorelease object CCMenu* pMenu = CCMenu::create(pCloseItem, NULL); pMenu->setPosition(CCPointZero); this->addChild(pMenu, 1); //创建场景 CCNode* gameScene = SceneReader::sharedSceneReader()->createNodeWithSceneFile("DemoCowboy.json"); addChild(gameScene); //创建角色 CCNode* playerNode = gameScene; player = new Player(playerNode); //创建按钮(控制左、右、射击) CCComRender *pRender = static_cast<CCComRender*>(playerNode->getChildByTag(10005)->getComponent("GUIComponent")); UILayer* ui = static_cast<UILayer*>(pRender->getNode()); UIButton * btnLeft = (UIButton*)ui->getWidgetByName("LeftButton"); btnLeft->addTouchEventListener(this, toucheventselector(HelloWorld::onMoveLeft)); UIButton* btnRight = (UIButton*)ui->getWidgetByName("RightButton"); btnRight->addTouchEventListener(this, toucheventselector(HelloWorld::onMoveRight)); UIButton* btnFire = (UIButton*)ui->getWidgetByName("FireButton"); // btnFire->addReleaseEvent(this, coco_releaseselector(HelloWorld::onFire)); btnFire->addTouchEventListener(this, toucheventselector(HelloWorld::onFire)); //Enable update loop this->scheduleUpdate(); return true; }更新事件及按钮触发事件
void HelloWorld::update(float dt) { player->update(dt); } void HelloWorld::onMoveLeft(cocos2d::CCObject *pSender, TouchEventType type) { if (type == TOUCH_EVENT_BEGAN) { player->setDirection(WALK_LEFT); player->setState(WALK); } if (type == TOUCH_EVENT_ENDED) { player->setState(IDLE); } } void HelloWorld::onMoveRight(cocos2d::CCObject *pSender, TouchEventType type) { if (type == TOUCH_EVENT_BEGAN) { player->setDirection(WALK_RIGHT); player->setState(WALK); } if (type == TOUCH_EVENT_ENDED) { player->setState(IDLE); } } void HelloWorld::onFire(cocos2d::CCObject *pSender, TouchEventType type) { if (type == TOUCH_EVENT_BEGAN) { player->setState(SHOOT); } if (type == TOUCH_EVENT_ENDED) { player->setState(IDLE); } }示例资源及代码: https://github.com/chukong/CocoStudioSamples