cocos2d-x 플랫폼별 해상도와 리소스 관련 정리

이번 포스팅에서는 cocos2d-x 기반으로 게임 개발할 때 이슈 내용인 해상도별 그래픽 리소스 관련 된 것을 정리해볼까 합니다. 회사나 1인 개발자들 또는 인디 팀에 따라 그래픽 리소스 개발 기준도 다를텐데요, 일단 아이폰 3gs가 지원하는 480 x 320과 레티나 디스플레이용인 960 x 480 2개의 리소스가 있다는 전제로 진행합니다.

여담이지만 일반 PC게임 만들때는 하나의 결정된 해상도의 리소스를 가지고 모든 해상도를 커버하겠지만, 아직은 하드웨어적인 발전 가능성이 큰 스마트 기기인 만큼 최적화를 위해서 지원 해상도별로 리소스 제작하는게 어쩔 수 없는 부분인 듯 싶네요.

플랫폼별 정리에 앞서 cocos2d-x에서 해상도와 리소스 관련 된 소스 부분들을 먼저 알아보겠습니다. 참고로 일단은 win32기반으로 설명을 진행하고, 나머지 android나 ios기반은 소스부분이 살짝 다를 수 있는데 이건은 따로 간략히 추가하는 식으로 진행하겠습니다.

먼저 제일 첫 부분인 main.cpp에 있는 부분입니다.

CCEGLView& eglView = CCEGLView::sharedOpenGLView();
eglView.setFrameSize(480, 320);
// set the design resolution screen size, if you want to use Design Resoulution scaled to current screen, please uncomment next line.//eglView.setDesignResolutionSize(480, 320);
해상도와 관련된 초기화가 진행되는 부분입니다. setFrameSize는 윈도우 사이즈를 결정하는데 쓰입니다. setDisignResolutionSize는 내부적으로 (윈도우 사이즈 / 디자인 사이즈)를 계산해서 ScreenScaleFactor를 설정하게 됩니다. 그리고 DesignResolutionSize에 Factor를 적용해서 뷰포트 사이즈가 산출 됩니다. 즉, 아래와 같습니다.
FrameSize > DesignResolutionSize : 이미지 확대
FrameSize = DesignResolutionSize : 사이즈 동일
FrameSize < DesignResolutionSize : 이미지 축소

CCEGLView *view = &CCEGLView::sharedOpenGLView();
view->setFrameSize(w, h);
// set the design resolution screen size, if you want to use Design Resoulution scaled to current screen, please uncomment next line.
// view->setDesignResolutionSize(480, 320);
android는 setFrameSize는 기기의 해상도로 설정이 바로 됩니다. ios도 기기 해상도로 설정 됩니다.

2번째로 레티나 디스플레이 관련 부분입니다.
// enable High Resource Mode(2x, such as iphone4) and maintains low resource on other devices.
// pDirector->enableRetinaDisplay(true);
레티나 디스플레이라는 것이 아이폰이나 아이패드, 맥북등에 쓰이는 물리적인 디스플레이에 대한 것이지만 cocos2d-x의 win32환경에서는 레티나 관련 된 api를 지원합니다. 아래 처럼 말이죠.

bool CCEGLView::canSetContentScaleFactor()
{
    return true;
}
true이면 최종적으로 윈도우 사이즈가 2배 뻥튀기가 되고 ContentScaleFactor라는 값도 2로 설정이 됩니다.( ContentScaleFactor나 위에서 나온 ScreenScaleFactor나 같은 놈으로 보시면 됩니다. 레티나 설정시 같이 설정이 되어집니다. 하지만 ScreenScaleFactor이 설정된다고 ContentScaleFactor까지 같아지진 않습니다. ) 이것은 추후 그래픽 리소스 로딩하거나 내부적으로 사이즈, 좌표 계산시 관련 된 부분입니다. 결과적으로 false이면 sd이미지를 true이면 파일명끝에 -hd 라고 붙는 레티나용 이미지 리소스를 로딩합니다.

const char* CCFileUtils::fullPathFromRelativePath(const char *pszRelativePath) 
Resource 폴더의 (ios에서는 Bundle개념, android는 _resource.apk의 패스 내부적으로 assets/) 리소스 파일 패스를 얻기 위해 위 함수를 사용하는데요, 먼저 윈도우 사이즈가 1024 x 768 이면 아이패드로 아니면 아이폰으로 구분합니다. 또한 위에 설명했던 ContentScaleFactor 값이 1이냐 아니냐에 따라 sh, hd를 구분하고 총 아이패드(-ipad), 레티나 디스플레이 아이패드(-ipadhd), 아이폰, 레티나 디스플레이 아이폰(-hd)으로 총 4가지로 분류되어 자동으로 파일명 끝에 관련 suffix를 붙여서 패스를 리턴합니다.

하지만 이것은 ios나 win32에만 국한된 내용이고 android는 그냥 던져주는 파일명 그대로를 리턴해주므로 추가로 -hd를 붙이거나 하지 않습니다.

bool CCEGLViewProtocol::canSetContentScaleFactor()
{
    return false;
}
또한 android에서는 지원이 안되기 때문에 enableRetinaDisplay를 true로 해도 위처럼 false를 리턴해서 먹히지 않습니다. 막아놨더군요.

자, 이제 이미지도 봐가면서 위에서 설명한 것을 플랫폼별로 확인해보도록 하겠습니다. 먼저 helloworld 샘플에 이미지 몇개 더 추가 및 수정을 해줬습니다.

fps_images.png
fps_images-hd.png

fps_images-ipadhd.png

HelloWorld-hd.png
아래 이미지만으로도 해상도가 어떻게 되었고 어떤 리소스가 로딩이 되었는지 아실 수 있을 것입니다.

win32
enableRetinaDisplay == false 일 때

eglView.setFrameSize(480, 320);



eglView.setFrameSize(960, 640);
원본이미지는 더 큽니다. 클릭해서 보세요.


eglView.setFrameSize(480, 320);
eglView.setDesignResolutionSize(480, 320);

eglView.setFrameSize(480, 320);
eglView.setDesignResolutionSize(960, 640);

윈도우 사이즈는 480인데 designResolutionSize가 960이 되어 480 / 960 즉, factor가 0.5가 되어 이미지가 축소 되었습니다.

eglView.setFrameSize(960, 640);
eglView.setDesignResolutionSize(480, 320);
위와 반대로 윈도우 사이즈가 더 큽니다. 해서 factor가 2가 되어 이미지가 2배 뻥튀기 되어 집니다. sd이미지를 뻥튀기 하게 된거죠.

eglView.setFrameSize(960, 640);
eglView.setDesignResolutionSize(960, 640);

enableRetinaDisplay == true 일 때
eglView.setFrameSize(480, 320);

레티나가 활성이 되어서 설정된 윈도우는 480이지만 자동으로 2배 뻥튀기 되어 960으로 됩니다. 내부적으로 리소스도 -hd 가 붙은 이미지를 로딩하게 되어 출력되고 있습니다.

eglView.setFrameSize(960, 640);
역시나 960에서 해상도가 2배 뻥튀기 되서 1920 1280이 되어버립니다.


eglView.setFrameSize(480, 320);
eglView.setDesignResolutionSize(480, 320);
assert에 걸려버립니다.
if (m_bNeedScale)
    {
        CCAssert(CC_CONTENT_SCALE_FACTOR() == 1.0f, "retina and scale mode can't be opened at the same time!");

이는 명시적으로 setDesignResolutionSize를 해주면 저 m_bNeedScale 때문에 무조건 걸리게 되어있습니다. setDesignResolutionSize 사이즈가 frameSize와 같던 크던 적던 상관없이 걸리므로 레티나 디스플레이가 true라면 setDesignResolutionSize는 명시적 호출을 하지 말아야합니다.

eglView.setFrameSize(480, 320);
eglView.setDesignResolutionSize(960, 640);

eglView.setFrameSize(960, 640);
eglView.setDesignResolutionSize(480, 320);

eglView.setFrameSize(960, 640);
eglView.setDesignResolutionSize(960, 640);


위 3개도 마찬가지로 다 assert가 된다는거죠.


android
안드로이드 빌드 귀찮아서 win32에서 해상도만 조절해서 찍은 샷으로 대체해 봅니다. 참고로 제 폰이 NesusS인데 800 x 480입니다.

eglView.setFrameSize(800, 480);
디바이스 해상도는 800 x 480인데 로딩 된 이미지는 일반 480 x 320이므로 화면을 다 채우지 못하게 나옵니다. 당연한 결과죠.


eglView.setFrameSize(800, 480);
eglView.setDesignResolutionSize(480, 320);
이번에는 DesignResolutionSize를 이미지와 같게해서 결과적으로 factor값이 2가 되어 2배 뻥튀기 된 화면입니다. 화면에 꽉차게 됩니다. 대신 480사이즈를 뻥튀기 한 것이라 이미지 퀄리티는 떨어져 보이게 되겠지요?

ios
따로 확인은 안았지만 별 탈없이 디바이스에 맞는 이미지가 로딩 될 것으로 믿습니다!

댓글

  1. 좋은 글 감사합니다.. 최근 cocos2dx를 이용하여 프로젝트를 진행하고 있는데 많은 도움이 되었습니다..
    그런데 2.0.2 version에서는 main.cpp에 setframe다음에 해주면 assert가 걸려버리더군요..
    AppDelegate.cpp에 setOpenGLView다음에 CCEGLView::sharedOpenGLView()->setDesignResolutionSize(960,640,kResolutionExactFit); 해주어야 제대로 동작하는것 같습니다

    답글삭제
    답글
    1. 네. 아마도 이번에 좀더 크로스플랫폼하게 해상도 관련된 피처들이 변경 추가되서 그런거 같네요. 크로스플랫폼 하려면 역시나 main.cpp가 아닌 AppDelegate.cpp에서 해주는게 맞겟죠. 저흰 rc2.2.0.1인가를 아직 쓰고 있어요. 조만간 개인 플젝은 2.0.2 스테이블 버전업 된걸 써봐야겠네요.

      삭제

댓글 쓰기

이 블로그의 인기 게시물

'xxx.exe' 프로그램을 시작할 수 없습니다. 지정된 파일을 찾을 수 없습니다.

goorm IDE에서 node.js 프로젝트로 Hello World Simple Server 만들어 띄워보기

애드센스 수익을 웨스턴 유니온으로 수표대신 현금으로 지급 받아보자.