jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid 캐나다로 날아가서 실력만으로 수석 그래픽 프로그래머가 된 꽃미남 프로그래머 "Pope Kim" 님의 북미취업 가이드북이 전자책으로 출간되었습니다. 블랙썬  for Kakao 클래식한 던젼 RPG와 간편한 모바일 게임이 만났다! 당신을 검과 마법의 장대한 서사시로 초대하는 [블랙썬 for Kakao]!! 3ds Max를 사용해서 게임용 3D 캐릭터를 셋업하는 방법
이를 위해 오랜 실무를 경험해 온 저자의 고급 노하우들이 공개
위 내용은 GameDevForever의 저자분들의 홍보를 위하여 운영진 자체적으로 올린 광고이며 일체의 수익이 없습니다.(밥좀사줘요~)
Posted by 친절한티스

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 );


저작자 표시 비영리 동일 조건 변경 허락

댓글을 달아 주세요

  1. Favicon of http://gbox3d.tistory.com 도플광어(圖佛光語) 2012/11/19 15:16  댓글주소  수정/삭제  댓글쓰기

    브롸보~!! 마침구현하려던건데 감사합니다

  2. 하늘향기 2013/01/04 11:25  댓글주소  수정/삭제  댓글쓰기

    허허헣

    plist로 하던걸 이렇게 관리 하니 편하군요;

  3. 잼난거 2014/05/13 16:31  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 유용한 글 잘 읽었습니다.
    cocos2d-x 초보자인지라 감안해주시면 감사하겠습니다.
    json 파일은 하나하나 손으로 써야하나요? 혹시 툴을 쓰셨는지 궁금해서요. ^^ 데이타가 많지 않으면 상관없지만 양이 꽤 되면 개발시간이 꽤 걸리니까요.
    SpriteBuilder를 통한 ccbi파일로 트라이해보다 버전 미스매치로 실패하고 간편한 애니메이션 데이타 파일 작성을 알아보고 있는데 정보가 많지 않네요.



티스토리 툴바