기초 튜토리얼 4-1
기초 튜토리얼 4 (번역 : n_Sys)
입문자 튜토리얼 4: Frame Listeners 와 Unbuffered Input
이 튜토리얼 진행중 문제가 발생한다면 Help 포럼 에 문의하세요.
Contents |
미리 알아두어야 할 것
이 튜토리얼은 독자가 C++ 프로그래밍이 가능하고 오우거 어플리케이션 설정 및 컴파일이 가능하다는 가정하에 진행됩니다.이 튜토리얼은 이전 튜토리얼을 기초로 작성되었으며 독자는 이전 튜토리얼들을 거쳐왔다고 가정합니다.
소개
이 튜토리얼에서는 오우거에서 가장 쓸만한 것 중 한가지인 FrameListener 를 소개합니다. 끝날 시점에서 어떻게 매 프레임마다 업데이트 동작을 하고 버퍼를 쓰지 않는 입력시스템을 사용하는 방법을 이해하게 될 것입니다.
이 튜토리얼을 위한 코드는 여기(http://www.ogre3d.org/wiki/index.php/BasicTutorial4Source)서 구할 수 있습니다. 튜토리얼을 천천히 진행함에 따라 프로젝트에 코드를 직접 입력하고 중간중간 생성되는 결과를 확인하세요.
시작하기
지난번 튜토리얼처럼 미리 작성된 코드를 기초로 시작할 것 입니다. 프로젝트를 생성하고 아래의 코드를 입력하세요 :
#include "ExampleApplication.h"
class TutorialFrameListener : public ExampleFrameListener
{
public:
TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr)
: ExampleFrameListener(win, cam, false, false)
{
}
// Overriding the default processUnbufferedKeyInput so the key updates we define
// later on work as intended.
bool processUnbufferedKeyInput(const FrameEvent& evt)
{
return true;
}
// Overriding the default processUnbufferedMouseInput so the Mouse updates we define
// later on work as intended.
bool processUnbufferedMouseInput(const FrameEvent& evt)
{
return true;
}
bool frameStarted(const FrameEvent &evt)
{
return ExampleFrameListener::frameStarted(evt);
}
protected:
bool mMouseDown; // Whether or not the left mouse button was down last frame
Real mToggle; // The time left until next toggle
Real mRotate; // The rotate constant
Real mMove; // The movement constant
SceneManager *mSceneMgr; // The current SceneManager
SceneNode *mCamNode; // The SceneNode the camera is currently attached to
};
class TutorialApplication : public ExampleApplication
{
public:
TutorialApplication()
{
}
~TutorialApplication()
{
}
protected:
void createCamera(void)
{
}
void createScene(void)
{
}
void createFrameListener(void)
{
}
};
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
int main(int argc, char **argv)
#endif
{
// Create application object
TutorialApplication app;
try {
app.go();
} catch(Exception& e) {
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occurred: %s\n",
e.getFullDescription().c_str());
#endif
}
return 0;
}
만약 오우거SDK 를 Windows 에서 사용한다면 "[OgreSDK_DIRECTORY]\samples\include" 디렉토리 (ExampleApplication.h 파일이 있는곳) 를 include 가 가능하도록 프로젝트에 추가해 주세요. 만약 오우거엔진 소스를 직접 사용하신다면 [OgreSource_DIRECTORY]\Samples\Common\include" 을 추가해 주세요. 다음 진행을 위해서 컴파일이 에러없이 되도록 해두세요. 그러나 실행은 하지마세요! 나중에 이 코드에 더 추가할 계획입니다. 만약 컴파일에 문제가 생긴다면 Wiki 페이지에서 컴파일러별 설정 정보를 찾아보시고 문제가 지속된다면 Help 포럼에 문의하세요.
조작컨트롤 부분은 튜토리얼을 진행하면서 완성될 것 입니다.
FrameListeners
소개
이전 튜토리얼에서는 createScene 함수에 추가한 내용만 다뤘습니다. 오우거에서는 화면에 한 프레임이 그려지기 전과 그려진 후의 알림을 전달받을 수 있는 클래스를 등록할 수 있습니다. 이 FrameListener 인터페이스는 2개의 함수를 정의합니다 :
bool frameStarted(const FrameEvent& evt)
bool frameEnded(const FrameEvent& evt)
오우거의 메인루프 (Root::startRendering) 는 다음과 같습니다 :
1. Root 객체가 등록된 모든 FrameListener들의 frameStarted함수를 호출.
2. Root 객체가 한 프레임을 출력.
3. Root 객체가 등록된 모든 FrameListener 들의 frameEnded 함수를 호출.
이 반복루프는 어느 한 FrameListener의 frameStarted 또는 frameEnded함수가 false 를 리턴하기 전까지 계속 돕니다. 이 함수들에 있어서 리턴값의 의미는 간단하게 말하자면 "계속 그려라" 입니다. 이것들 중 하나가 거짓을 리턴하면 프로그램은 종료됩니다. FrameEvent 객체가 2개의 변수를 가지고 있지만 FrameListener에서는 timeSinceLastFrame값만 유용하게 사용됩니다. 이 값은 frameStarted 또는 frameEnded 함수가 마지막으로 실행되고나서 얼마나 긴 시간이 흘렀는지를 추적합니다. 참고로 frameStarted 함수에서 FrameEvent::timeSinceLastFrame은 마지막 frameStarted가 호출된 시간으로부터 지금까지 지난 시간을 가집니다 (frameEnded가 마지막으로 호출된 시간이 아닙니다).
오우거에서 다수의 FrameListener에 있어서 중요한 속성중 하나는 어떤 순서로 호출될지의 여부는 오우거엔진에게 달려있다는 점 입니다. 어떤순서로 FrameListener 들이 호출될지 예측할 수 없습니다. 만약 다수의 FrameListener들의 호출순서를 정하고 싶다면 하나의 FrameListener 만 등록하고 관련된 모든 객체들의 호출들의 순서를 적절히 설정하면 됩니다.
메인루프는 세가지 단계만 실행된다는 것을 아셨을 겁니다. frameEnded와 frameStarted 사이에는 아무런 일이 일어나지 않으므로 코드를 바꿔쓰셔도 됩니다. 코드를 작성하는 위치를 정하는것은 전적으로 유저에게 달렸습니다. 하나의 큰 frameStarted나 frameEnded에 몰아서 작성하셔도 되고 두개에 나눠서 작성하셔도 됩니다.
FrameListener 등록하기
지금까지 작성한 코드는 컴파일이 되지만 ExampleApplication클래스의 createFrameListener와 createCamera함수를 재정의 했으므로 실행시킨다면 종료시킬 수 없게 됩니다. 계속 진행하기전에 여기서 이 문제점을 해결해야 합니다.
TutorialApplication::createCamera 함수를 찾아서 다음 코드를 추가하세요 :// create camera, but leave at default position
mCamera = mSceneMgr->createCamera("PlayerCam");
mCamera->setNearClipDistance(5);
아직 이걸로는 아무것도 완성된 것이 아닙니다. 이 함수를 재정의한 이유는 기존의 createCamera함수에서 카메라를 움직이고 방향을 바꿔버리기 때문입니다. 그러한 기능은 이 튜토리얼에서는 필요없는 부분입니다.
Root 클래스가 한 프레임을 그릴때 FrameListener도 같이 관여합니다. 첫째로 해야할 일은 TutorialFrameListener 인스턴스를 만들고 Root 객체에 등록합니다. TutorialApplication::createFrameListener 를 찾아서 다음 코드를 추가하세요.
// Create the FrameListener
mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr);
mRoot->addFrameListener(mFrameListener);
ExampleApplication 클래스에는 mRoot와 mFrameListener 변수가 정의되어 있습니다. addFrameListener 함수는 FrameListener를 추가하고 removeFrameListener 함수는 FrameListener를 제거합니다(이 말은 FrameListener가 더 이상 새로운 메세지를 받지 않게 된다는 말입니다). 알아 두실점은 add|removeFrameListener 함수는 FrameListener 의 포인터만 받습니다 (FrameListener를 제거할때 이름이 사용되지 않음을 의미합니다). 그러므로 각각의 FrameListener 포인터를 보관하고 계셔야 나중에 메모리에서 제거할 수 있습니다.
ExampleFrameListener(TutorialFrameListener 가 상속받은 클래스) 역시 showDebugOverlay (불리언 리턴) 함수를 제공합니다. 이 함수는 ExampleApplication 클래스에게 프레임율을 보여주는 작은 박스를 화면 왼쪽 구석에 표시할지 여부를 설정합니다. 다음과 같이 on 으로 설정합니다 :
// Show the frame stats overlay
mFrameListener->showDebugOverlay(true);
계속 진행하기전에 컴파일 하고 실행이 가능하도록 해주세요.
장면(Scene) 설정
소개
코드를 살펴보기 전에 이해를 돕고자 화면에 무엇을 넣을것인지에 대한 간략한 설명을 드리고자 합니다.
닌자 한명과 Point 조명하나를 배치시킬 것 입니다. 마우스왼쪽버튼을 누르면 조명이 on/off 됩니다. 오른쪽 버튼을 누르고 있으면 "Mouse Look" 모드가 됩니다(카메라로 사방을 자유롭게 둘러볼 수 있습니다). 다수의 SceneNode 를 장면에 배치하여 서로다른 뷰포트를 가지는 카메라들이 attach 될 수 있도록 할 것입니다. "1", "2" 키로 보고 싶은 뷰포트를 선택합니다.
소스코드
TutorialApplication::createScene 함수로 갑시다. 먼저 희미한 주변광(ambient)을 설정합니다. 조명이 off 되더라도 객체가 보여야 하며, on 된 상태와는 또 구별되어야 합니다 :
mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25));
원점위치에 닌자 Entity 를 추가합니다 :
Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");
SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");
node->attachObject(ent);
이제 Point조명을 장면에 추가하고 닌자로부터 조금 떨어진 거리에 위치시킵니다 :
Light *light = mSceneMgr->createLight("Light1");
light->setType(Light::LT_POINT);
light->setPosition(Vector3(250, 150, 250));
light->setDiffuseColour(ColourValue::White);
light->setSpecularColour(ColourValue::White);
이제 SceneNode 에 카메라를 attach 하는일이 남았군요 :
// Create the scene node
node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400));
node->yaw(Degree(-45));
node->attachObject(mCamera);
// create the second camera node
node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400));
TutorialApplication 클래스에서 해야 할일은 끝입니다. 이제 TutorialFrameListener 로 들어갑니다...