티스토리 뷰

스프라이트 애니메이션은 게임에서 가장 많이 쓰이는 기능 중 하나일 것이다. Cocos2d-x에서는 나름대로 CCAnimate, CCAnimatio이라는 클래스를 만들어서 쉽게 스프라이트 애니메이션을 만들 수 있도록 하려하지만.... 솔직히 그닥 편리하진 않다. (물론 일일이 새로 만들어야 하는 것보다는 훨씬 편하긴 하다.)

아무래도 스프라이트 애니메이션에 필요한 텍스쳐를 두고 애니메이션 데이터를 따로 갖고 있는 것이 관리에는 더욱 편리하다. 보통은 왼쪽에서 오른쪽으로 균일한 크기의 프레임으로 주르륵 배열을 하기 마련인데, 이러면 빈 공간들, 그리고 겹치는 이미지를 처리해주지 않는 문제 때문에 용량의 낭비가 발생한다. 스프라이트 애니메이션이 한 두개일 경우에는 상관없지만 여러개가 되면 용량 낭비가 누적되는데 모바일 게임에서 리소스의 용량은 적잖이 중요한 이슈이기 때문에 조심해야한다. 

애니메이션 데이터를 갖고 있을 파일의 포맷은 여러가지가 될 수 있겠지만 나는 JSON이 좋더라. (사실은 이전에 참고했던 블로그 글에서 JSON을 이용하는 내용이 있었다. 이 글은 이 분이 만드신 클래스를 좀 더 자세히 설명하는 수준이라 볼 수도 있을 것이다. 친절한티스님의 블로그 글 : http://kindtis.tistory.com/491) 이 글에서 소개하고 있는 소스를 Cocos2d-x 2.2 버전에 맞춰서 약간 마이그레이션을 해봤다. 필요하신 분들은 쓰시라고 파일 첨부를 한다. 아래 그림 파일의 경우 상업적 사용을 제외하면 자유롭게 사용해도 상관없다.




MyAnimation.cpp


MyAnimation.h


star_sprite_animation.json


아래 코드는 사용 방법을 보여준다.


// Create animatino class

CCNode *anim = MyAnimation::create( "gamescene/star_sprite_animation.json" );

// Add object that is made to scene

layer->addChild( anim );

// Run animation sequence

anim->PlayWithSequence( "eaten" );


Animation을 생성할 때 json 파일을 넘겨준다. json 파일에는 어떤 그림 파일을 텍스쳐로 활용하는지("image" 필드), 그리고 각각의 프레임이 어떻게 되는지("batchnode" 필드)가 들어있어야 한다. 각각의 프레임에는 이름과 텍스쳐의 어떤 부분에 해당하는지에 대한 정보를 담아야 한다. 

이제는 sequence를 만들 차례. 각각의 sequence는 이름과 각 프레임을 몇초동안 보여주는지, 그리고 어떤 프레임들을 쓰는지를 갖고 있게 된다. 여기서 각 프레임을 몇 초동안 보여주는지를 수정하여 해당 sequence가 총 몇 초동안 보여져야 하는지로 수정할 수도 있을 것이다. (물론 이럴려면 클래스를 약간 수정하긴 해야한다.) 



다음 코드는 MyAnimation 클래스의 내용이다.



const int CHILD_SPRITE_BATCH = 10;


class MyAnimation : public CCNode

{

private:

CCSprite *_spr;

map< string, CCAnimate* > _sequenceList; 


public:

MyAnimation();

~MyAnimation();


static MyAnimation* create( const char *jsonPath );


bool initWithJson( const char *jsonPath );


void PlayWithSequence( const char *seqName );

};


MyAnimation* MyAnimation::create( const char* jsonPath )

{

MyAnimation *pNewNode = new MyAnimation;

pNewNode->autorelease();

if( pNewNode->initWithJson( jsonPath ) )

return pNewNode;


CC_SAFE_DELETE( pNewNode );

return NULL;

}


bool MyAnimation::initWithJson( const char* jsonPath )

{

do {

CC_BREAK_IF( jsonPath == NULL );

unsigned long size = 0;

const char *pData = (char*)(cocos2d::CCFileUtils::sharedFileUtils()->getFileData(jsonPath, "r", &size));

CC_BREAK_IF(pData == NULL || strcmp(pData, "") == 0);

cs::CSJsonDictionary *jsonDict = new cs::CSJsonDictionary();

jsonDict->initWithDescription(pData);

string strImageFileName = jsonDict->getItemStringValue("image");

CCSpriteBatchNode *pSpriteBatch = CCSpriteBatchNode::create( strImageFileName.c_str() );

addChild( pSpriteBatch, 0, CHILD_SPRITE_BATCH );

// Making batch node

map< string, CCSpriteFrame* > spriteFrameList;

int batchNodeSize = jsonDict->getArrayItemCount("batchnode");

for( int batchIndex = 0; batchIndex < batchNodeSize; ++batchIndex ) {

cs::CSJsonDictionary *batchNode = jsonDict->getSubItemFromArray("batchnode", batchIndex);

string frameName = batchNode->getItemStringValue("name");

int xPos = batchNode->getIntValueFromArray("rect", 0, 0);

int yPos = batchNode->getIntValueFromArray("rect", 1, 0);

int width = batchNode->getIntValueFromArray("rect", 2, 0);

int height = batchNode->getIntValueFromArray("rect", 3, 0);

CCSpriteFrame *pSpriteFrame =

CCSpriteFrame::createWithTexture(pSpriteBatch->getTexture(), CCRectMake( xPos, yPos, width, height));

spriteFrameList.insert(make_pair(frameName, pSpriteFrame));

if( batchIndex == 0 ) {

_spr = CCSprite::createWithSpriteFrame( pSpriteFrame );

pSpriteBatch->addChild( _spr );

}

CC_SAFE_DELETE(batchNode);

}

// Make sequences

int sequenceSize = jsonDict->getArrayItemCount("sequence");

for( int sequenceIndex = 0; sequenceIndex < sequenceSize; ++sequenceIndex )

{

cs::CSJsonDictionary *sequenceNode = jsonDict->getSubItemFromArray("sequence", sequenceIndex);

string sequenceName = sequenceNode->getItemStringValue("name");

float animationDelay = sequenceNode->getItemFloatValue("delay", 1);

CCAnimation *pAnim = CCAnimation::create();

pAnim->setDelayPerUnit( animationDelay );

int numOfFrame = sequenceNode->getArrayItemCount("node");

for( int frameIndex = 0; frameIndex < numOfFrame; ++frameIndex ) {

string sequenceFrameName = sequenceNode->getStringValueFromArray("node", frameIndex);

map< string, CCSpriteFrame* >::iterator it;

it = spriteFrameList.find(sequenceFrameName);

if( it == spriteFrameList.end() )

continue;

CCSpriteFrame *pFrame = it->second;

pAnim->addSpriteFrame(pFrame);

}

CCAnimate *newSequence = CCAnimate::create(pAnim);

newSequence->retain();

_sequenceList.insert(make_pair(sequenceName, newSequence));

CC_SAFE_DELETE(sequenceNode);

}

CC_SAFE_DELETE(jsonDict);

CC_SAFE_DELETE(pData);

return true;

} while(0);

return false;

}


void MyAnimation::PlayWithSequence( const char *seqName )

{

map< string, CCAnimate* >::iterator it;

it = _sequenceList.find( seqName );  

if( it == _sequenceList.end() )  

return;  


CCSequence *seq = (CCSequence*)CCSequence::create( it->second, NULL );  

_spr->runAction( seq );

}



딱 필요한 부분만 추려서 코드를 썼다. 방식은 간단하다. 애니메이션 sequence들을 갖고 있으며 필요할 때 play를 요청하면 sequence 리스트에서 애니메이션을 가져와 play 하게 된다. (자료구조는 map을 쓰긴 했지만 표현을 편하게 하기 위해 리스트라고 하였다.) create나 initWith~ 함수는 cocos2d-x의 기존 컴포넌트들의 생성 방식과 비슷하다. 

initWithJson 함수에서는 주어진 json 파일을 파싱해 애니메이션 데이터를 가져와 클래스를 초기화한다. 사용한 Json 파서는 CocoStudio에서 사용하는 라이브러리 부분이다. (CSJsonDictionary 클래스) 2.2 이전 버전에서는 마땅한 Json 파서가 없어서 spine에서 쓰던 Json 파서 부분을 썼는데, CocoStudio가 추가되면서 이 CocoStudio 것을 쓰는걸로 변경했다. CocoStudio가 cocos2d-x 팀에서 열심히 미는 패키지니까 아마 당분간 이게 사라지는 것에 대한 걱정은 안 해도 되지 않을까. 어쨌든 json 파일에서 텍스쳐를 불러온 뒤 Frame array 데이터를 파싱하여 각각의 이름을 가진 프레임을 생성한다. 마지막으로 Sequence array 데이터를 파싱하여 앞에서 생성해둔 프레임을 이용하여 애니메이션을 만들고 그것을 저장한다.

PlayWithSequence 함수에 play할 애니메이션 sequence 이름을 넣어주면 예쁘게 애니메이션이 재생되는 것을 볼 수 있다. 딱 보면 알겠지만 sequence 리스트에서 키값을 이용해 애니메이션을 가져와 스프라이트에 runAction 해주는 방식이다.


이 글이 다른 분들에게 유용하게 쓰였으면 좋겠다. 이만.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday