Wstęp Spróbujemy pokazać w jaki sposób i w jakim celu można użyć wielu Menadżerów Sceny w jednej aplikacji OGRE. Będziemy używać klasy map STL. Warto zapoznać się z nią przed rozpoczęciem pracy. Zastosujemy też operator warunkowy ?: z C++, warto byś znał jego składnię i możliwości.
Rozpoczęcie Podobnie jak w poprzednio oprzemy się na zalążku programu OGRE, który należy wkleić w pierwszy utworzony plik .cpp. Uzupełnimy go o klasę SMTutorialListener. Odszukaj linię:
// Dziedziczymy ExampleApplication
i przed nią wstaw poniższy kod:
#define CAMERA_NAME "SceneCamera"
class SMTutorialListener : public ExampleFrameListener { public: SMTutorialListener(RenderWindow* win, Camera* cam) : ExampleFrameListener(win, cam, false, false)
bool frameStarted(const FrameEvent &evt)
// Pozwala wykonać metodę z klasy rodzica return ExampleFrameListener::frameStarted(evt);
protected: MultiSceneManager *mMSM; SceneType mSceneType; // Bieżący typ menadżera sceny
Prócz nowej klasy zdefiniowaliśmy również nazwę kamery, która ułatwi nam później tworzenie kodu. Kod ten nie nadaje się na razie do skompilowania. Musimy z tym poczekać, aż zdefiniujemy klasę MultiSceneManager.
Dodatkowe zmienne i metody w myApp Do klasy myApp dodamy jedną zmienną mMSM, która będzie zawierała obiekt klasy MultiSceneManager. Tworzymy też metodę createFrameListener, w której zarejestrujemy nasz FrameListener. Kod wstawiamy zaraz za dyrektywą protected: w klasie myApp.
MultiSceneManager mMSM; // Klasa MultiSceneManager
void createFrameListener(void)
mFrameListener = new SMTutorialListener(mWindow, mCamera); mFrameListener->showDebugOverlay(true); mRoot->addFrameListener(mFrameListener);
Zanim pójdziemy dalej Będziemy używać tu kilku menadżerów sceny w jednej aplikacji. Ogólnie mówiąc wykonuje się to w trzech ruchach. Najpierw zlikwidować trzeba wszystkie Viewport-y i kamery połączone z bieżącym menageram sceny. Po drugie należy wziąć manager sceny który teraz chcesz wykorzystać i zarejestrować go w Root::_setCurrentSceneManager. Wreszcie trzeba odtworzyć kamery i viewport-y. To naprawdę prosty proces. W naszym przykładzie zbudujemy bardzo prostą klasę MultiSceneManager która umożliwi nam przełączanie pomiędzy różnymi menadżerami sceny. W następnym przykładzie rozszerzymy tę prostą klasę tak by mogła zarządzać róznymi kontekstami tego samego menadżera sceny.
Oczywiście zauważysz że najwiecej pracy jak zwykle mają klasy z Example Framework. Example Framework został napisany z założeniem że zmienna mCamera nie będzie zmieniana po utworzeniu, co nie ułatwia sprawy gdy zmieniamy menadżera sceny. Chciałem zachować Example Framework w całum Tutorialu tak, żebyś nie musiał uczyć sie nowej struktury podczas nauki innych rzeczy. Jednakże gdybyś chciał budować co poważniejszego musisz zaprzestać używania Example Framework i napisać swoje własne klasy aplikacji i FrameListener-a dostosowane do twoich celów.
Wreszcie, zauważ że kolejność klas w pliku źródłowym musi być właściwa albo program nie da się skompilować. Kolejność powinna być następująca:
// #defines // SceneManagerState class // MultiSceneManager class // SMTutorialListener class // SMTutorialApplication class // main method
Klasa SceneManagerState Zamierzamy zdefiniować dwie klasy umożliwiające wymienianie menadżerów sceny. Klasa MultiSceneManager będzie publicznym interfejsem do zamiany menadżerów, a SceneManagerState będzie zachowywać ślady zmiennych menadżera który zostaje zlikwidowany kiedy dokonujemy zamiany.
Wprowadź ten kod zaraz za linią :
// SceneManagerState class SceneManagerState
public: SceneManagerState( SceneManager *sceneManager, RenderWindow *renderWindow ) : mSceneManager( sceneManager ), mRenderWindow( renderWindow )
~SceneManagerState( )
// called when this SceneManager is being displayed void showScene( )
// called when this SceneManager is no longer being displayed void hideScene( )
// returns the SceneManager SceneManager *getSceneManager( )
return mSceneManager;
protected: Vector3 mCamPosition; // The camera position Quaternion mCamOrientation; // The camera orientation SceneManager *mSceneManager; // The SceneManager this object wraps RenderWindow *mRenderWindow; // The RenderWindow the application uses
Klasa ta zawiera podstawowe informacje na temat kamery, ponieważ kiedy niszczymy kamerę tracimy też jej pozycję i orientację. W momencie, gdy klasa ta ma załadować menadżera sceny, wywoływana jest także metoda showScene. Natomiast przy wyładowaniu menadżera wywoływana jest metoda hideScene.
Metoda showScene musi stworzyć kamerę, ustawić proporcje, a także odtworzyć pozycję i orientację kamery. Dodaj poniższy kod do metody showScene:
// create the camera and viewport Camera *cam = mSceneManager->createCamera( CAMERA_NAME ); Viewport *vp = mRenderWindow->addViewport( cam ); vp->setBackgroundColour(ColourValue(0,0,0));
// Set the aspect ratio cam->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight())); cam->setNearClipDistance( 5 );
// Set the camera's position and orientation cam->setPosition( mCamPosition ); cam->setOrientation( mCamOrientation );
Metoda hideScene musi zapisać pozycję i orientację kamery a następnie zlikwidować wszystkie viewport-y i kamery. Dodaj poniższy kod do metody hideScene:
// save camera position and orientation Camera *cam = mSceneManager->getCamera( CAMERA_NAME ); mCamPosition = cam->getPosition(); mCamOrientation = cam->getOrientation();
// destroy the camera and viewport mRenderWindow->removeAllViewports( ); #if OGRE_VERSION removeAllCameras( ); #else mSceneManager->destroyAllCameras( ); #endif
Nasz kod nie może zostać skompilowany, dopóki nie zdefiniujemy klasy MultiSceneManager.
Klasa MultiSceneManager Drugą częścią systemu jest klasa MultiSceneManager. By użyć takiego obiektu, trzeba najpierw zarejestrować go w obiekcie Root i w obiekcie RenderWindow. Następnie musisz zarejestrować każdy typ menadżera sceny jaki chcesz użyć, na przykład ST_GENERIC, ST_EXTERIOR_CLOSE, itd. Gdy już ustawisz obiekty Root/RenderWindow i zarejestrujesz typy menadżerów sceny, będziesz mógł użyć tej klasy i ustawiać swoje menadżery sceny.
By skonfigurować menadżer sceny (na przykład ustawić wstępny widok), wywołaj metodę getSceneManager z typem menadżera sceny który chcesz użyć. Metoda zwróci obiekt menadżera sceny. Przy ustawianiu, czy przełączaniu po prostu wywołuje się metodę setSceneManager z typem menadżera.
Jądro klasy MultiSceneManager dodajemy na początku definicji klas.
// MultiSceneManager class MultiSceneManager
public: MultiSceneManager( ) : mCurrentState( 0 )
~MultiSceneManager( )
// sets the Root object void setRoot( Root *root )
mRoot = root;
// sets the RenderWindow void setRenderWindow( RenderWindow *renderWindow )
mRenderWindow = renderWindow;
// registers a scene manager class for usage, you cannot call // setSceneManager on any type without first calling registerSceneManager void registerSceneManager( SceneType st )
// makes st the current SceneManager void setSceneManager( SceneType st )
// returns the SceneManager that's currently rendering SceneManager *getCurrentSceneManager( )
return mCurrentState ? mCurrentState->getSceneManager() : 0;
// returns the SceneManager associated with type st (must already be registered) SceneManager *getSceneManager( SceneType st )
protected: // finds and returns the SceneManagerState associated with st SceneManagerState *findState( SceneType st )
std::map mStateMap; // The map of registered states SceneManagerState *mCurrentState; // the current state Root *mRoot; // the Root object RenderWindow *mRenderWindow; // the RenderWindow
Zaczynamy z metodą registerSceneManager. Metoda dodaje nowy element mStateMap dla wyspecyfikowanego SceneType. Dokonujemy tego tworząc objekt SceneManagerState i kojarząc go z kluczem st:
mStateMap[ st ] = new SceneManagerState( SceneManagerEnumerator::getSingleton().getSceneManager( st ), mRenderWindow );
W Ogre 1.2, zamień getSceneManager na createSceneManager.
Użyliśmy operatora new, musimy więc później pamiętać o usunięciu obiektu. W destruktorze usuniemy elementy mapy. Najpierw utworzymy iterator, następnie iterujemy poprzez elementy mapy, usuwając obiekty SceneManagerState (dodaj ten kod do ~MultiSceneManager):
std::map::iterator itr;
for (itr = mStateMap.begin( ); itr != mStateMap.end( ); ++itr) delete itr->second;
The itr->second points to the SceneManagerState, whereas itr->first would point to the SceneType that we use as a key for the map.
We will now implement the helper function findState. The findState function searches the map for the specified state, and returns it (or 0 if it's not found).
// Search map std::map::iterator itr = mStateMap.find( st );
// return the result or 0 return (itr != mStateMap.end( )) ? itr->second : 0;
Now that findState is implemented, we can implement the getSceneManager function easily. The getSceneManager function returns the SceneManager assocated with the SceneType specified, but does NOT set it as the current SceneManager. We will first search for the requested SceneType, then return it (or 0 if the SceneType was not registered):
SceneManagerState *sms = findState(st); return sms ? sms->getSceneManager( ) : 0;
The last function we have to implement is the setSceneManager method. The basic flow of the method is to first hide the current SceneManager, find the requested SceneManager and start it rendering:
// hide the current SceneManager if ( mCurrentState ) mCurrentState->hideScene( );
// find the next SceneManager mCurrentState = findState( st );
// set the current SceneManager in the Root class after initializing // the state if ( mCurrentState )
mCurrentState->showScene( ); mRoot->_setCurrentSceneManager( mCurrentState->getSceneManager() );
That wraps up the MultiSceneManager class. Be sure your code compiles before continuing.
Klasa SMTutorialApplication The first thing we have to do in the SMTutorialApplication class is stop the ExampleApplication class from trying to create cameras and viewports on its own. To do this, override the createCamera and createViewports with empty functions in the protected portion of the class:
// We overide this method with an empty class and hide the base method // since all of this functionality is contained in the chooseSceneManager // method now. void createCamera()
// We overide this method with an empty class and hide the base method // since all of this functionality is contained in the chooseSceneManager // method now. void createViewports()
Now we need to set up the MultiSceneManager class. We do this by adding code to the chooseSceneManager method. First we register the information that the MultiSceneManager class needs to operate. We cannot pass these as constructor values, since at the time the mMSM object is constructed the Root and RenderWindow variables are not yet set.
// Set the required information mMSM.setRoot( mRoot ); mMSM.setRenderWindow( mWindow );
Now we need to register the SceneTypes that we will be using in the application. You may register these as you use them instead of all at the beginning if you like. The only restriction is that a SceneType must be registered before you call setSceneManager or getSceneManager with that type.
// Register SceneTypes we will be using: mMSM.registerSceneManager( ST_GENERIC ); mMSM.registerSceneManager( ST_EXTERIOR_CLOSE );
Now we need to set the startup SceneManager:
// Set the startup SceneType: mMSM.setSceneManager( ST_GENERIC );