기초 튜토리얼 7-3 (마지막)
Ogre3D 삽질란/Basic Tutorial 7 2008. 12. 4. 17:22
윈도우, 시트, 위젯
소개
CEGUI는 다른 GUI시스템들에 비해서 좀 독특합니다. CEGUI에 있어서 출력되는 모든 것들은 CEGUI::Window클래스의 상속된 형태이며 하나의 윈도우는 여러개의 자식 윈도우들을 가질 수 있습니다. 이 말은 여러개의 버튼을 포함하는 하나의 프레임을 만든다면 프레임이 윈도우라는 의미입니다. 이런 특징으로 이상한 현상을 유발할 수도 있습니다. 실제로는 절대 쓰이지 않겠지만 버튼 속에 또 다른 버튼을 배치할 수도 있습니다. 왜 이런 사항들을 언급하냐면 만약에 어떤 위젯을 프로그램에 배치시킨 상황에서 나중에 포인터를 다시 찾고 싶을때가 있을 것 입니다. 모든 위젯들은 윈도우들에 의해 호출되어 집니다. 그리고 호출했던 윈도우의 함수를 사용함으로써 접근이 가능합니다.
CEGUI를 사용하는 대부분의 경우 각각의 객체들을 코드로 생성하지는 않을 것 입니다. 대신에 CEGUI 레이아웃 에디터같은 툴을 사용해서 프로그램에서 필요로 하는 GUI 레이아웃을 작성합니다. 모든 윈도우, 버튼, 위젯을 화면상에 배치시킨 다음 레이아웃을 텍스트파일로 저장합니다. 나중에 CEGUI용 GUI시트(이것역시 CEGUI::Window에서 상속받은 형태)에 로드시킬 수 있습니다.
마지막으로 알아두실 것은 CEGUI는 정말 많은 위젯들을 포함하고 있씁니다. 이 모든것들을 이 튜토리얼안에서 다룰 수는 없으므로 CEGUI를 사용하기로 마음먹었으면 잘 꾸미기위해 CEGUI공식 홈페이지를 방문하셔서 많은 정보를 얻으시길 바랍니다.
시트 읽어들이기
CEGUI에서 시트를 읽는 작업은 무척 쉽습니다. WindowManager클래스가 제공하는 “loadWindowLayout” 함수로 시트를 읽어들이고 CEGUI::Window객체에 넣을 수 있습니다. 그런 다음 CEGUI::System::setGUISheet함수로 출력할 수 있습니다만 이 튜토리얼에서는 사용하지 않을 것 입니다. 하지만 적어도 어떻게 쓰는지 예제라도 보여드려야 죄책감이 들지 않을것 같군요. 이 튜토리얼용 코드에 추가하지 마세요(만약에 추가했다면 결과를 확인하고 해당 코드를 삭제해 주세요) :
// Do not add this to the program
CEGUI::Window* sheet = CEGUI::WindowManager::getSingleton().loadWindowLayout((CEGUI::utf8*)"ogregui.layout");
mSystem->setGUISheet(sheet);
이 코드는 지금 보여질 시트를 선택합니다. 나중에 이 시트의 포인터는 System::getGUISheet함수를 통해서 다시 구할 수 있습니다. 그리고 GUI시트를 다른 어떤 시트와도 setGUISheet함수를 통해서 매끄럽게 교체할 수도 있습니다(나중에 현재의 시트를 다시 복구하고 싶다면 포인터를 저장해 두세요).
수동으로 객체 생성하기
앞서 얘기한 대로 CEGUI를 쓰면서 GUI시트는 에디터를 써서 만들게 될겁니다. 하지만 가끔씩은 수동으로 위젯을 화면상에 올릴 필요도 있습니다. 예를 들어서 나중에 기능을 추가시킬 Quit 버튼을 추가할 겁니다. 튜토리얼을 진행하면서 Quit버튼 밖에도 더 많은 것들을 추가할 계획이고 최초로 생성될 디폴트 CEGUI::Window는 앞으로 만들어질 모든 위젯들을 포함할 것 입니다. 다음 코드를 createScene함수에 추가하세요 :
CEGUI::WindowManager *win = CEGUI::WindowManager::getSingletonPtr();
CEGUI::Window *sheet = win->createWindow("DefaultGUISheet", "CEGUIDemo/Sheet");
이 코드는 WindowManager를 사용해서 "CEGUIDemo/Sheet"라는 이름을 가진 "DefaultGUISheet"를 생성합니다. 시트이름은 마음대로 지을 수 있습니다. 게다가 이렇게 "SomeApp/MainMenu/Submenu3/CancelButton"처럼 계층적방식으로 위젯 이름을 짓는것은 매우 흔한(그리고 추천되는) 방식입니다. 다음으로 해야 할 일은 Quit버튼을 생성하고 크기를 설정하는 것 입니다 :
CEGUI::Window *quit = win->createWindow("TaharezLook/Button", "CEGUIDemo/QuitButton");
quit->setText("Quit");
quit->setSize(CEGUI::UVector2(CEGUI::UDim(0.15, 0), CEGUI::UDim(0.05, 0)));
무슨 암호문을 써 놓은것 처럼 보이는군요. CEGUI는 크기와 위치에 대해서 "unified dimension" 시스템을 사용합니다. 크기를 설정할때는 반드시 UDim객체를 만들어서 원하는 크기를 설정한다음 생성될 객체에게 알려줘야 합니다. 첫번째 매개변수는 부모객체에대한 상대적인 크기입니다. 두번째 매개변수는 절대적인 객체크기(필셀단위)입니다. 중요한게 하나 있는데 두개의 매개변수들 중 반드시 한가지 매개변수만 설정해야 합니다. 설정하지 않은 매개변수는 반드시 0이 되어야 합니다. 그래서 이 코드의 경우는 부모객체크기의 15%가로길이와 5%의 세로길이를 가집니다. 만약 20x5픽셀크기로 설정하고 싶다면 2가지 UDim객체들의 두번째 매개변수를 20과 5를 각각 설정하면 됩니다.
마지막으로 해야 할 일은 시트에 Quit버튼을 attach하는 일 입니다. 그 다음 현재 GUI시트를 시스템에 적용 시킵니다 :
sheet->addChildWindow(quit);
mSystem->setGUISheet(sheet);
이제 실행시켜보면 화면의 좌측 상단에 Quit버튼이 자리잡고 있음을 확인할 수 있을겁니다. 그러나 아직까지는 클릭해도 아무런 변화가 없습니다.
이벤트
CEGUI에서 이벤트는 유연한 성질을 가지고 있습니다. 이벤트를 받기위해 인터페이스를 사용하는것 대신에 콜백 방식을 이용해서 사용자가 원하는 public 함수(특정한 함수형태를 만족하는)에 바인딩 시켜서 이벤트를 다룰 수 있습니다. 아쉽게도 이벤트를 등록시키는 작업은 오우거의 경우보다 약간 더 복잡한 절차를 가집니다. 이제 Quit버튼의 클릭이벤트에 프로그램 종료기능을 등록해 봅시다. 그러기 위해서 이전 섹션에서 만든 Quit버튼의 포인터가 필요합니다. TutorialListener생성자로 가서 다음 코드를 추가하세요 :
CEGUI::WindowManager *wmgr = CEGUI::WindowManager::getSingletonPtr();
CEGUI::Window *quit = wmgr->getWindow((CEGUI::utf8*)"CEGUIDemo/QuitButton");
이제 Quit버튼 포인터를 얻었습니다. 이제는 클릭이벤트를 작성해 봅시다. 모든 CEGUI용 위젯들은 사용가능한 이벤트셋트를 가지고 있으며 "Event"라는 이름으로 시작합니다. 이것이 클릭이벤트를 기술하는 코드입니다 :
quit->subscribeEvent(CEGUI::PushButton::EventClicked,
CEGUI::Event::Subscriber(&TutorialListener::quit, this));
subscribeEvent의 첫번째 매개변수는 자기 자신에 대한 이벤트입니다. 두번째 매개변수는 Event::Subscriber객체입니다. Subcriber가 생성될때 첫번째로 넘겨주는 값은 이벤트를 다루게 될 함수입니다(& 기호는 함수의 포인터를 가져다 줍니다). 두번째로 넘겨주는 값은 이벤트를 다룰 TutorialListener의 객체입니다(이번 경우는 "this" 객체). 완성입니다! TutorialListener::quit함수(미리 작성되어진)는 마우스클릭이벤트를 다루며 프로그램을 종료시킵니다.
컴파일 후 실행시켜서 테스트해 보세요.
CEGUI에서 이벤트를 다루는 함수들의 생성횟수에는 아무런 제약이 없습니다. 한가지 제약사항은 반드시 불리언타입의 결과값을 리턴해야 하며 1개의 "const CEGUI::EventArgs &" 방식의 매개변수만을 취해야 한다는 것 입니다. 이벤트들에 대해서 더 알고 싶은게 있다면(그리고 어떻게 이벤트등록을 취소시키는지) CEGUI 공식홈페이지를 참고하세요.
텍스쳐에 렌더링하기
CEGUI로 할 수 있는 흥미로운 작업중 하나는 Render To Texture 윈도우를 생성하는 것 입니다(RTT 튜토리얼- http://www.ogre3d.org/wiki/index.php/RTT). RTT를 이용하면 CEGUI위젯으로 직접적으로 그려지는 또다른 뷰포트를 생성할 수 있게 됩니다. 먼저 해야 할 일은 바라볼 장면을 설정해야 합니다. 다음 코드를 createScene의 하단에 추가하세요 :
mSceneMgr->setAmbientLight(ColourValue(1, 1, 1));
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
Entity* ogreHead = mSceneMgr->createEntity("Head", "ogrehead.mesh");
SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(Vector3(0, 0, -300));
headNode->attachObject(ogreHead);
이제 RenderTexture를 생성해야 합니다. RenderSystem객체는 텍스쳐에 렌더링하는 기능을 제공합니다. 일단 TextureManager::createManual함수를 이용해서 텍스쳐를 하나 생성합니다. 이 프로그램에서는 512 x 512크기의 텍스쳐를 사용할 계획입니다 :
RenderTexture *
("R2TTex", "Default", TEX_TYPE_2D, 512, 512, 0, PF_R8G8B8, TU_RENDERTARGET)
->getBuffer()->getRenderTarget();
이 함수에 대한 자세한 정보는 API 레퍼런스를 참고하세요. 다음으로 해야 할 일은 카메라와 앞서 작성된 장면을 바라보는데 쓰일 뷰포트를 생성하는 것 입니다. Overlay를 끈것을 포함해서 뷰포트 옵션들 중 2개가 바뀐것에 유의하세요. 이 것은 매우 중요합니다. 그렇지 않으면 CEGUI를 얻고 작은 윈도우안에 오우거 Overlay를 표시하게 될 겁니다.
Camera *cam = mSceneMgr->createCamera("R2TCam");
cam->setPosition(100, -100, -400);
cam->lookAt(0, 0, -300);
Viewport *v =
v->setOverlaysEnabled(false);
v->setClearEveryFrame(true);
v->setBackgroundColour(ColourValue::Black);
다름아닌 텍스쳐 내부에 뷰포트를 추가했습니다(평상시 뷰포트를 추가하던 RenderWindow의 경우와는 다릅니다). 여기까지 장면과 텍스쳐를 만들었습니다. 이제는 CEGUI안에 내장시킬 차례입니다. OgreCEGUIRenderer::createTexture함수를 사용해서 그 어떤 오우거 텍스쳐에서도 CEGUI::Texture를 생성할 수 있습니다 :
CEGUI::Texture *cTex = mRenderer->createTexture((CEGUI::utf8*)"R2TTex");
아쉽게도 좀 복잡해지는 부분을 하나 언급하고자 합니다. CEGUI를 사용하면서 1개의 텍스쳐나 1개의 이미지만을 사용할 수는 없습니다. CEGUI는 개별 이미지들 대신에 이미지셋트를 사용합니다. 전체가 격자모양으로 구성된 이미지셋트는 스킨의 Look and Feel을 구성할때 매우 유용하게 쓰입니다(예제로 SDK에 포함된 media폴더안의 TaharezLook.tga파일을 열고 어떻게 구성되어 있는지 보세요). 하지만 비록 1개 이미지만 사용해야 한다해도 전체 이미지셋트를 생성해야 합니다. 즉 이렇게 해야 합니다 :
CEGUI::Imageset *imageSet = CEGUI::ImagesetManager::getSingleton().createImageset((CEGUI::utf8*)"R2TImageset", cTex);
imageSet->defineImage((CEGUI::utf8*)"R2TImage",
CEGUI::Point(0.0f, 0.0f),
CEGUI::Size(cTex->getWidth(), cTex->getHeight()),
CEGUI::Point(0.0f,0.0f));
첫번째 라인에서 미리 제공한 텍스쳐로부터 이미지셋트("R2TImageset")를 생성합니다. 다음라인(defineImage호출부분)에서는 첫부분이자 유일한 이미지 "R2Timage"를 설정하고 크기를 전체 cTex텍스쳐와 동일하게 맞춰줍니다. 마지막으로 렌더텍스쳐를 표시할 StaticImage 위젯을 생성해야 합니다. 첫번째 부분은 다른 윈도우생성과 동일합니다 :
CEGUI::Window *si = win->createWindow((CEGUI::utf8*)"TaharezLook/StaticImage", "R2TWindow");
si->setSize(CEGUI::UVector2(CEGUI::UDim(0.5f, 0), CEGUI::UDim(0.4f, 0)));
si->setPosition(CEGUI::UVector2(CEGUI::UDim(0.5f, 0), CEGUI::UDim(0, 0)));
이제 출력될 StaticImage위젯을 설정합니다. 다시 언급하자면 CEGUI는 각각의 이미지들 대신에 이미지셋트를 다루므로 이미지가 출력되기 위해서는 전체 이미지셋트로부터 정확한 이미지이름을 추출해 줘야 합니다 :
si->setProperty("Image", CEGUI::PropertyHelper::imageToString(&imageSet->getImage((CEGUI::utf8*)"R2TImage")));
만약에 한개의 이미지를 그냥 unpack시키기 위해서 이미지셋트로 pack시키는 작업처럼 보였다면 제대로 보신겁니다. CEGUI에서 이미지를 조작하는 작업은 쉽지 않은일 입니다. 마지막으로 해야 할 일은 StaticImage위젯을 미리 만들어 두었던 GUI시트에 추가하는 것 입니다 :
sheet->addChildWindow(si);
이제 끝입니다. 컴파일 후 실행하세요.
결론
대안으로 쓸만 한 것들
CEGUI 는 좀 희안하고 단점도 있습니다. 그리고 절대 모든 사용자를 위한 라이브러리도 아닙니다. 여기 CEGUI 대신에 쓸만한 대안들이 있습니다 :
더 자세한 정보
CEGUI 대해서 좀 더 알고 싶다면 이 사이트들을 참고하세요.
Practical Application - Something With A Bit More Meat - A more in-depth tutorial than the one here.