cocos2d에서는 스프라이트 시트를 이용한 스프라이트 애니메이션 기능을 지원하고 있습니다. 샘플 프로젝트를 통해 작동 방법을 살펴보면 plist ( XML 포맷 ) 를 통해 스프라이트 정보를 구성하여 애니메이션을 재생하는 식으로 동작하고 있습니다. 대략적인 샘플 코드를 살펴보죠.


// 배치 스프라이트 
CCSpriteBatchNode *pSpriteBatch = CCSpriteBatchNode::create( "Resources/grossini.png" );
addChild( pSpriteBatch, 0, CHILD_SPRITE_BATCH );

CCSpriteFrameCache *frameCache = CCSpriteFrameCache::sharedSpriteFrameCache();
frameCache->addSpriteFramesWithFile( "Resources/grossini.plist" );

// 스프라이트 객체 생성
CCSprite *pSprite = CCSprite::create();
CCSpriteFrame *frame = frameCache->spriteFrameByName("grossini_dance_01.png");
pSprite->setDisplayFrame(frame);
pSpriteBatch->addChild(pSprite);

// 애니메이션 리스트
CCAnimationCache *animCache = CCAnimationCache::sharedAnimationCache();
animCache->addAnimationsWithFile( "Resources/animations.plist" );

CCAnimation *normal = animCache->animationByName( "dance_1" );
normal->setRestoreOriginalFrame(true);
CCAnimate *animN = CCAnimate::create(normal);
CCSequence *seq = (CCSequence*)CCSequence::create(animN, NULL);

pSprite->runAction( CCRepeatForever::create( seq ) );


스프라이트 시트


grossini.plist 내부를 보면 위와 같이 XML 포맷으로

각 프레임에 대한 스프라이트 좌표가 설정 되어있습니다.



animation.plist 에는 해당 시퀀스에 쓰이는 장면들의 정보가 설정 되어있습니다.


이것만으로도 충분하겠지만 개인적으로 좀더 욕심이 생기더군요. 우선 plist의 XML 포맷이 너무 보기 안좋습니다. 쓸데없는 문자열도 많이 들어가서 정보량이 많아지면 기하급수적으로 덩치도 커지고, cocos2d-x에서 plist를 사용하는 방식도 너무 정형화되어 있어 커스터마이즈도 힘들어 보이더군요.


그래서 json 포맷을 이용해 위와 같은 동작을 하는 동시에 커스터마이즈도 용이한 방법을 구현해봤습니다( 내용적으로는 거의 동일 합니다. ). 먼저 json으로 위의 grossini.plist와 animation.plist를 하나로 통합했습니다.



훨씬 깔끔하고 보기에도 좋습니다. 이제 이 데이터를 기반으로 위와 같이 스프라이트 애니메이션을 구현해보겠습니다.


// JSON 파싱
Json::Reader jsonReader;
Json::Value rootValue;
bool parsingSuccessful = jsonReader.parse( ifstream( strFileName.c_str() ), rootValue );
if( !parsingSuccessful )
	return parsingSuccessful;

// 배치 스프라이트를 읽어온다
string strImageFileName = rootValue.get( "image", "null" ).asString();
CCSpriteBatchNode *pSpriteBatch = CCSpriteBatchNode::create( strImageFileName.c_str() );
addChild( pSpriteBatch, 0, CHILD_SPRITE_BATCH );

// 배치노드 파싱
const Json::Value batchNode = rootValue[ "batchnode" ];
assert( batchNode.isArray() );
int batchNodeSize = batchNode.size();
hash_map< string, CCSpriteFrame* > spriteFrameList;
CCAnimation * anim = CCAnimation::create();
for( int batchIndex = 0; batchIndex < batchNodeSize; ++batchIndex )
{
	const Json::Value spriteValue = batchNode[ batchIndex ];
	string frameName = spriteValue.get( "name", "null" ).asString();
	const Json::Value spriteRectValue = spriteValue[ "rect" ];
	assert( spriteRectValue.isArray() );
	assert( 4 == spriteRectValue.size() );

	int jsonArrayIndex = 0;
	int xPos = spriteRectValue[ jsonArrayIndex++ ].asInt();
	int yPos = spriteRectValue[ jsonArrayIndex++ ].asInt();
	int width = spriteRectValue[ jsonArrayIndex++ ].asInt();
	int height = spriteRectValue[ jsonArrayIndex++ ].asInt();

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

	// 0번을 기본 스프라이트로
	if( 0 == batchIndex )
	{
		m_pSprte = CCSprite::createWithSpriteFrame( pSpriteFrame );
		pSpriteBatch->addChild( m_pSprte );
	}

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

// 시퀀스 정보를 파싱
const Json::Value sequenceList = rootValue[ "sequence" ];
assert( sequenceList.isArray() );
int sequenceSize = sequenceList.size();
for( int sequenceIndex = 0; sequenceIndex < sequenceSize; ++sequenceIndex )
{
	const Json::Value sequenceValue = sequenceList[ sequenceIndex ];
	string sequenceName = sequenceValue.get( "name", "null" ).asString();
	float animationDelay = (float)sequenceValue.get( "delay", 1 ).asDouble();
	CCAnimation *pAnim = CCAnimation::create();
	pAnim->setDelayPerUnit( animationDelay );

	const Json::Value sequenceFrame = sequenceValue[ "node" ];
	assert( sequenceFrame.isArray() );
	int sequenceFrameSize = sequenceFrame.size();
	for( int sequenceIndex = 0; sequenceIndex < sequenceSize; ++sequenceIndex )
	{
		string sequenceFrameName = sequenceFrame[ sequenceIndex ].asString();
		hash_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 );
	m_sequenceList.insert( make_pair( sequenceName, newSequence ) );
}

이후에 원하는 시퀀스를 플레이해주고 싶을때 아래와 같이 호출해주면 됩니다.


hash_map< string, CCAnimate* >::iterator it;
it = m_sequenceList.find( strSequenceName );
if( it == m_sequenceList.end() )
	return;

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


+ Recent posts