티스토리 뷰

다중 해상도를 지원하는 것은 상당히 골치 아픈 일이다. 굉장히 많은 방법이 있겠지만 내가 나름대로 삽질을 거듭하면서 괜찮다 싶은 솔루션을 공유해보려고 한다.


나는 Design Resolution과 Resource Search Path, Content Scale Factor를 활용하기로 했다. 이 글에서는 이 속성들을 깔끔하게 잡는 법에 대해 설명하려고 한다. 



나는 AppDelegate.cpp에서 Design Resolution을 재가공 해주는 방안을 선택했다. 왜 재가공 해주어야 하는가? 


세상에는 정말 다양한 해상도의 기기들이 많다. 문제는 이 기기들의 종횡비도 제멋대로라는 것이다. 아이폰만 해도 960x640, 1136x640은 종횡비가 다르다. 거기다가 아이패드까지 생각하면 1024x768으로 큰 차이를 보인다. 하지만 나는 UI Tool(나는 SpriteBuilder를 쓰기로 했다.)을 이용해서 하나의 해상도 상에서 UI를 배치한 후, 모든 기기에 그 레이아웃을 적용하고 싶었다. 


처음에는 Design Resolution을 고정한 채로 어떻게 해결해보려고 했다. 그런데 아무리 생각해도 답이 안 나오더라.... (setDesignResolutionSize 함수에 여러 Policy를 적용해가면서 온갖 삽질을 다 해봤다.)


결론적으로 Policy는 NoBoarder로 고정했다. ShowAll이나 ExtractFit은 말할 필요도 없이 패스. FixedWidth 혹은 FixedHeight 두 가지도 테스트를 해봤는데, 이 경우 아이폰5 이상부터 Horizontal 모드 기준 가로가 늘어나버리고, 아이패드는 세로가 너무 긴 바람에 곤란했다. 



1. Design Resolution


setDesignResolutionSize 함수 내부를 보면 GLView를 스케일링하는 코드가 적혀있다. 이것을 보고 Design Resolution 자체 크기를 변경해버리면 어떨까 하는 생각을 하게 되었다. Design Resolution의 종횡비가 달라지는 것은 어차피 SpriteBuilder에서 UI를 배치할 때 Left-Top, Right-Top, Left-Bottom, Right-Bottom 기준으로 배치를 하기 때문에 괜찮겠다 싶었다. 


그 코드는 아래와 같다.

CCSize designedResolution = pGame->GetDesignedResolution();
CCSize frameSize = pDirector->getOpenGLView()->getFrameSize();

float fScaleX = (float)frameSize.width / designedResolution.width;
float fScaleY = (float)frameSize.height / designedResolution.height;


float ratio;
if( fScaleX > fScaleY )
{
	ratio = frameSize.width / designedResolution.width / fScaleY;
	designedResolution.width *= ratio;
}
else
{
	ratio = frameSize.height / designedResolution.height / fScaleX;
	designedResolution.height *= ratio;
}

CCLog( "final designed resolution : %.2f, %.2f", designedResolution.width, designedResolution.height );
pDirector->getOpenGLView()->setDesignResolutionSize( designedResolution.width, designedResolution.height, kResolutionNoBorder );


간단하게 설명하면 기기의 해상도(Frame Size)의 비율과 맞게 Design Resolution을 수정하는 것이다.



2. Content Scale Factor와 Resource Search Path


Content Scale Factor를 잡을 때의 문제는 '적절함'이었다. medium이나 small 사이즈 기준으로는 문제가 없었으나, large 사이즈에서는 삐끗나는 경우가 자주 보였기 때문이다. 그것 때문에 다시 처음부터 하고 싶진 않아서 찾은 해결방안이 바로 floor 함수였다. 코드는 다음과 같다.

typedef struct tagResource
{
    cocos2d::CCSize size;
    char directory[100];
}Resource;

static Resource smallResource  =  { cocos2d::CCSizeMake(480, 320),   "small" };
static Resource mediumResource =  { cocos2d::CCSizeMake(1138, 640),  "medium"   };
static Resource largeResource  =  { cocos2d::CCSizeMake(2048, 1536), "large" };

CCSize frameSize = pDirector->getOpenGLView()->getFrameSize();

std::vector< std::string > searchPath;
if (frameSize.width > mediumResource.size.width)
{
	searchPath.push_back(largeResource.directory);

	float scaleFactor = MAX( largeResource.size.height / designedResolution.height, largeResource.size.width / designedResolution.width );
	scaleFactor = floorf( scaleFactor );
	pDirector->setContentScaleFactor( scaleFactor );
}
else if (frameSize.width > smallResource.size.width)
{
	searchPath.push_back(mediumResource.directory);

	float scaleFactor = MAX( mediumResource.size.height / designedResolution.height, mediumResource.size.width / designedResolution.width );
	pDirector->setContentScaleFactor( scaleFactor );
}
else
{
	searchPath.push_back(smallResource.directory);

	float scaleFactor = MAX( smallResource.size.height / designedResolution.height, smallResource.size.width / designedResolution.width );
	pDirector->setContentScaleFactor( scaleFactor );
}

// set searching path
CCFileUtils::sharedFileUtils()->setSearchPaths(searchPath);



HelloCpp의 예제와 거의 비슷한데 약간 다른 점은 largeResource로 등록하는 부분에서  Content Scale Factor를 계산한 뒤 floorf 연사를 취한다는 점이다. 어떻게 설명하기 난감한데.... 이렇게 하면 1920x1080, 1920x1200, 2048x1536 같은 대표적인 해상도에서 적절해진다 -_-;;



3. 순서

중요한 것은 상기한 내용은 2번을 코드에 먼저 써주고, 1번을 써줘야 한다는 것이다. 아무래도 Design Resolution이 변수라서 그렇다. 따라서 AppDelegate.cpp의 applicationDidFinishLaunching 함수 내부에서 다중 해상도 지원을 위한 코드는 다음과 같다.

CCSize designedResolution = pGame->GetDesignedResolution();
CCSize frameSize = pDirector->getOpenGLView()->getFrameSize();

std::vector< std::string > searchPath;
if (frameSize.width > mediumResource.size.width)
{
	searchPath.push_back(largeResource.directory);

	float scaleFactor = MAX( largeResource.size.height / designedResolution.height, largeResource.size.width / designedResolution.width );
	scaleFactor = floorf( scaleFactor );
	pDirector->setContentScaleFactor( scaleFactor );
}
else if (frameSize.width > smallResource.size.width)
{
	searchPath.push_back(mediumResource.directory);

	float scaleFactor = MAX( mediumResource.size.height / designedResolution.height, mediumResource.size.width / designedResolution.width );
	pDirector->setContentScaleFactor( scaleFactor );
}
else
{
	searchPath.push_back(smallResource.directory);

	float scaleFactor = MAX( smallResource.size.height / designedResolution.height, smallResource.size.width / designedResolution.width );
	pDirector->setContentScaleFactor( scaleFactor );
}

// set searching path
CCFileUtils::sharedFileUtils()->setSearchPaths(searchPath);


float fScaleX = (float)frameSize.width / designedResolution.width;
float fScaleY = (float)frameSize.height / designedResolution.height;

float ratio;
if( fScaleX > fScaleY )
{
	ratio = frameSize.width / designedResolution.width / fScaleY;
	designedResolution.width *= ratio;
}
else
{
	ratio = frameSize.height / designedResolution.height / fScaleX;
	designedResolution.height *= ratio;
}

CCLog( "final designed resolution : %.2f, %.2f", designedResolution.width, designedResolution.height );
pDirector->getOpenGLView()->setDesignResolutionSize( designedResolution.width, designedResolution.height, kResolutionNoBorder );





끝! 다음에는 SpriteBuilder에 대해 포스팅해보겠다. 

(cocos2d-x에서 쓸 수 있게 하느라 엄청 삽질했다....;;)

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