Rozpoczęcie Podobnie jak w poprzednim rozdziale oprzemy się na zalążku programu OGRE, który należy wkleić w pierwszy utworzony plik .cpp. Uzupełnimy go o klasę TutorialFrameListener. Odszukaj linię:
// Dziedziczymy ExampleApplication
i przed nią wstaw następujący kod:
class TutorialFrameListener : public ExampleFrameListener { public: TutorialFrameListener( RenderWindow* win, Camera* cam, SceneManager *sceneMgr ) : ExampleFrameListener(win, cam, false, false)
bool frameStarted(const FrameEvent &evt)
return ExampleFrameListener::frameStarted( evt );
protected: bool mMouseDown; // Czy w ostatniej klatce lewy przycisk myszy był wciśnięty Real mToggle; // Ile czasu ma minąć, do odebrania następnego wydarzenia Real mRotate; // Stała rotacji Real mMove; // Stała przesunięcia SceneManager *mSceneMgr; // Bieżący menadżer sceny SceneNode *mCamNode; // Węzeł sceny, do którego jest powiązana kamera
Dodajmy też dwie metody do naszej klasy myApp. Odszukaj linię:
/** createScene jest funkcją czysto wirtualną w ExampleApplication,
i przed nią wstaw:
void createCamera(void)
void createFrameListener(void)
Frame listener Wprowadzenie W poprzednim rozdziale zajmowaliśmy się tylko tym co możemy wstawić do metody createScene. Tym razem zajmiemy się klasą FrameListener, która definiuje dwie ważne w naszych rozważaniach funkcje:
bool frameStarted(const FrameEvent& evt) bool frameEnded(const FrameEvent& evt)
Ogrowa pętla główna (Root::startRendering) wygląda mniej więcej tak:
Korzeń (Root) wywołuje metodę frameStarted na wszystkich zarejestrowanych FrameListenerach. Korzeń renderuje jedną klatkę. Korzeń wywołuje metodę frameEnded na wszystkich zarejestrowanych FrameListenerach.
Pętla ta wykonuje się, dopóki żaden z FrameListenerów nie zwróci false z funkcji frameStarted lub frameEnded. Wartość, którą zwracają te funkcje określa, czy dalej ma kontynuować rendering. Zatem jeśli zostanie zwrócone false, program zostanie zakończony. Obiekt FrameEvent przechowuje dwie zmienne, ale tylko jedna - timeSinceLastFrame - jest przydatna w FrameListenerze. Zmienna ta przechowuje, ilość czasu jaka upłyneła między ostatnim a obecnym wywołaniem frameStarted lub frameEnded.
Jednym z ważnych spraw w FrameListenerach, jest to, że porządek w jakim są one wywoływane jest zależne całkowicie od Ogre. Nie możesz określić, który FrameListener ma zostać wywołany pierwszy, który drugi, trzeci itd... Jeśli chcesz mieć pewność, że FrameListenery mają zostać wywołane w odpowiednim porządku, powinieneś zarejestrować tylko jednego FrameListenera i z niego wywoływać wszystkie pozostałe w odpowiedniej kolejności.
Rejestrowanie Utworzony wyżej kod można skompilować by sprawdzić czy wszystko wprowadziliśmy poprawnie. Jednak nie należy uruchamiać skompilowanego programu, gdyż po nadpisaniu metod createFrameListener i createCamera z klasy ExampleApplication zawiśnie nam i trzeba będzie go skilować. Musimy zatem obie metody rozbudować.
Znajdź funkcję myApp::createCamera i dodaj do niej poniższy kod:
// utworzy kamerę, ale zostawi domyślną pozycję mCamera = mSceneMgr->createCamera("PlayerCam"); mCamera->setNearClipDistance(5);
Utworzyliśmy kamerę o nazwie "PlayerCam" i ustawiliśmy najbliższą możliwą odległość widzenia (to co jest bliżej nie będzie widziane).
Ponieważ klasa Root jest tym, co renderuje klatki, więc musimy jej wskazać ścieżkę do FrameListenera. Najpierw utworzymy nową instancję naszej klasy TutorialFrameListener a następnie zarejestrujemy ją w korzeniu. W tym celu znajdź metodę myApp::createFrameListener i dodaj do niej poniższy kod:
// Tworzy FrameListener mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr); mRoot->addFrameListener(mFrameListener);
Zmienne mRoot i mFrameListener są już określone w klasie ExampleApplication. Metoda addFrameListener rejestruje FrameListenera, natomiast removeFrameListener wyrejestrowywuje go co oznacza, że tak wyrejestrowany FrameListener nie będzie więcej dostawał żadnych informacji. Argumentem metody addFrameListener i removeFrameListener jest wskaźnik do FrameListenera (nie możesz operować FrameListnerem podając jego nazwę, bo takiej nie posiada). Warto zachowywać wskaźnik do każdego FrameListenera, choćby po to by go, gdy już nie jest potrzebny, usunąć z pamięci.
Gdybyśmy teraz uruchomili nasz program, zobaczylibyśmy czarny ekran, gdyż nic nie umieściliśmy na scenie. Warto byłoby jednak wprowadzić jakiś widoczny element, choćby po to by wiedzieć, że program pracuje poprawnie.
Klasa ExampleFrameListener (którą dziedziczy nasz TutorialFrameListener) dostarcza funkcję showDebugOverlay( bool ), która określa, czy pokazywać w lewym dolnym rogu informacje o szybkości klatek. Możemy go włączyć w ten sposób:
// Mówi, aby pokazywać ramkę ze statystykami mFrameListener->showDebugOverlay(true);
Od wersji Ogre 1.2 (Dagon) okienko informacji jest domyślnie włączone, więc moglibyśmy pominąc ten kawałek kodu. Wprowadzenie go jednak nie ma także żadnych ujemnych skutków.
Teraz skompilujmy i uruchommy nasz program.
Tworzymy scenę Wprowadzenie Przed przejściem do pracy z kodem, omówimy po krótce, co będziemy robić.
Umieścimy jeden obiekt w scenie (ninja) i dodamy jedno światło. Jeśli przyciśniesz lewym przyciskiem myszy, światło będzie włączane lub wyłączane. Trzymając wciśniety prawy przycisk myszy będziesz mógł obracać kamerą. Będziemy także przemieszczać węzły sceny powiązane z kamerą różnych viewportów. Wciskając przycisk 1 lub 2 zmieniamy punkt, z którego patrzy kamera.
Kod Znajdź metodę myApp::createScene. Pierwszą rzeczą jaką zrobimy będzie ustawienie światła otoczenia, ponieważ chcemy, aby obiekty w scenie były zawsze widoczne, nawet wtedy kiedy światło jest wyłączone.
mSceneMgr->setAmbientLight( ColourValue( 0.25, 0.25, 0.25 ) );
Następnie dodamy jednostkę z ninją:
Entity *ent = mSceneMgr->createEntity( "Ninja", "ninja.mesh" ); SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode( "NinjaNode" ); node->attachObject( ent );
Potem tworzymy punktowe źródło białego światła i umieszczamy je w scenie, w małej odległości od ninji:
Light *light = mSceneMgr->createLight( "Light1" ); light->setType( Light::LT_POINT ); light->setPosition( Vector3(250, 150, 250) ); light->setDiffuseColour( ColourValue::White ); light->setSpecularColour( ColourValue::White );
Musimy teraz utworzyć węzeły sceny, do której przywiążemy kamery. Ważną rzeczą jest to, że kiedy używamy systemu kamer musimy posiadać dodatkowe oddzielne węzły sceny przeznaczone do rotacji każdej kamery. Tak więc utwórzmy węzeł sceny i skierujmy go do ninji:
// Tworzymy węzeł sceny node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400)); //Ustawiamy kamerę na ninje node->yaw(Degree(-45)); node->attachObject(mCamera);
//Tworzymy drugi węzeł node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400));
Kiedy już to zrobimy, możemy przejść do prac w klasie TutorialFrameListener...
TutorialFrameListener
Zmienne Zdefiniowaliśmy kilka zmiennych w klasie TutorialFrameListener:
bool mMouseDown; // Czy w ostatniej klatce lewy przycisk myszy był wciśnięty Real mToggle; // Ile czasu ma minąć do odebrania następnego wydarzenia Real mRotate; // Stała rotacji Real mMove; // Stała przesunięcia SceneManager *mSceneMgr; // Bieżący menadżer sceny SceneNode *mCamNode; // Węzeł sceny, do którego jest powiązana kamera
mSceneMgr przechowuje wskaźnik do bierzącego menadżera sceny, natomiast mCamNode aktualny węzeł sceny, do którego jest "podczepiona" kamera (chodzi o "CamNode*", nie "PitchNode*"). mRotate i mMove są zmiennymi, przechowywującymi informacje o obracaniu i przesuwaniu. Jeśli chcesz, żeby rotacja lub przesuwanie było szybsze, to zwiększ te wartości, jeśli mają być wolniejsze to je odpowiedni zmniejsz.
Zmienne mToggle i mMouseDown kontrolują nasze wejście. Zastosujemy niebuforowane wejście (buforowane zostanie omówione w kojenym rozdziale). Będziemy je wykorzystywali aby sprawdzić stan klawiatury lub myszy.
Przejdźmy teraz do pewnego interesującego problemu, związanego z użyciem klawiatury w Ogre testowanym co każdą klatkę. Jeśli przyciśniemy klawisz, program może wykonać odpowiednią instrukcję, ale co się stanie przy następnej klatce? Ruch palca jest dłuższy niż czas zmiany klatek, a zatem dostaniemy informację że klawisz znów jest wciśnięty. Czy program ma znów wykonać czynności wykonane przy poprzedniej klatce? W niektórych przypadkach (np. ruch przy pomocy strzałek) tego właśnie byśmy oczekiwali. Problemem stanie się jednak obsługa klawisza przełaczającego np. wyłączającego/włączającego światło. Wtedy, gdy w pierwszej klatce, światło zostaje włączone, w następnej zostaje wyłączone, potem włączone, wyłączone itd., dopóki przycisk nie zostanie opuszczony. Ominąć ten problem możemy zachowując stan klawisza z poprzedniej klatki. Można stosować różne metody przy czym w naszym programie zastosujemy dwie różne.
Zmienna mMouseDown przechowuje informacje, czy lewy przycisk myszy był wciśniety w ostatniej klatce (czyli jeśli mMouseDown ma wartość true, drugi raz nie wykonujemy tej samej akcji, dopóki klawisz nie zostanie puszczony). Zmienna mToggle określa, jaki najkrótszy czas musi upłynąć pomiędzy wciśnięciem klawisza i wykonaniem jakiegoś działania a ponowną możliwością wykonania tej samej czynności. Innymi słowy nie dopuszcza do szybszego przełączania niż po upływie czasu w niej podanego.
Konstruktor Zobaczmy najpierw, jak wykonujemy konstruktor z klasy nadrzędnej ExampleFrameListener:
: ExampleFrameListener(win, cam, false, false)
Zapewne zuważyliśmy, że trzeci i czwarty argument są ustawione na false. Trzeci określa, czy korzystać z buforowanego wejścia, natomiast czwarty, czy buforować wejście myszy (jak na razie nie wykorzystujemy ich).
Wprowadzimy teraz do konstruktora TutorialFrameListener domyślne wartości dla wszystkich zmiennych:
// informacje o stanach klawiatury i myszy mMouseDown = false; mToggle = 0.0; // Wkładanie kamery w odpowiednie miejsce i ustawianie odpowiedniego menadżera sceny mCamNode = cam->getParentSceneNode( )->getParentSceneNode( ); mSceneMgr = sceneMgr; // ustawia szybkość przesunięcia i obrotu mRotate = 0.13; mMove = 250;
Wykonaliśmy dwa razy getParentSceneNode z obiektu cam, ponieważ pierwszym rodzicem tego obiektu jest PitchNode, a dopiero jego CamNode.
Metoda frameStarted Zajmiemy się teraz elementem stanowiącym jądro naszego wykładu - wykonywaniem pewnych działań co każdą klatkę. Aktualnie metoda frameStarted posiada poniższy kod:
return ExampleFrameListener::frameStarted( evt );
Ten krótki kod umożliwiał nam uruchomienie i sprawdzenie naszego programu bez potrzeby głębszego "dłubania". Funkcja ExampleFrameListener::frameStarted pozwala na obsłużenie wielu zdarzeń (np. związane z przyciskaniem przycisków, przesuwania kamery itp.). Wyczyść zawartość tej metody.
Od wersji 1.4 wprowadzono do Ogre Orientowany Obiektowo System Wejścia z ang. Object Oriented Input System, w skrócie OIS. Zastąpił on zmienną mInputDevice wprowadzając trzy nowe klasy do obsługi myszy, klawiatury oraz joysticka. W związku z powyższym informacje podane poniżej moga być nieaktualne i kompilator zakomunikuje o błędach. Poprawna wersja tego tutorialu znajduje się pod adresem http://www.ogre3d.org/wiki/index.php/Basic_Tutorial_4. Przełączanie światła klawiszem myszki Ponieważ używamy niebuforowanego wejścia, musimy pobrać bieżący stan klawiatury i myszy. Zrobimy to wywołując metodę OIS::Object::capture. Dodajmy, że klasa ExampleApplication zawiera deklaracje zmiennych mMouse i mKeyboard, na rzecz których wywołujemy funkcję capture():
mMouse->capture(); mKeyboard->capture();