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ę MoveDemoListener. Odszukaj linię:

// Dziedziczymy ExampleApplication

i przed nią wstaw poniższy kod:

#include using namespace std;

class MoveDemoListener : public ExampleFrameListener { public:

MoveDemoListener(RenderWindow* win, Camera* cam, SceneNode *sn, Entity *ent, deque &walk) : ExampleFrameListener(win, cam, false, false), mNode(sn), mEntity(ent), mWalkList( walk )

} // MoveDemoListener

// Funkcja ta jest wywoływana podczas przemieszczania obiektu do następnych pozycji w mWalkList. bool nextLocation( )

return true; } // nextLocation( )

bool frameStarted(const FrameEvent &evt)

return ExampleFrameListener::frameStarted(evt);

protected: Real mDistance; // Dystans który pozostał do przesunięcia Vector3 mDirection; // Kierunek ruchu Vector3 mDestination; // Punkt docelowy

AnimationState *mAnimationState; // Bieżący stan animacji jednostki

Entity *mEntity; // Jednostka którą animujemy SceneNode *mNode; // Węzeł sceny tejże jednostki std::deque mWalkList; // Lista zawierająca punkty ruchu

Real mWalkSpeed; // Szybkość poruszającej się jednostki

Dodatkowo kazaliśmy kompilatorowi włączyć do kompilacji kod źródłowy implementujący kolejki STL (deque).

Zmienne Do klasy myApp wprowadzamy parę niezbędnych dalej zmiennych. Niniejszy kod wstaw zaraz za dyrektywą protected w klasie myApp.

Entity *mEntity; // Jednostka którą będziemy animować SceneNode *mNode; // Węzeł sceny dla poruszającej się jednostki std::deque mWalkList; // Lista zawierająca punkty ruchu

mEntity będzie wskazywać na utworzoną jednostkę, mNode na węzeł, a mWalkList będzie przechowywać wszystkie punktu, po których będzie chodził obiekt.

Rejestracja FrameListenera Jak pamiętamy z poprzednich przykładów musimy zarejestrować klasę FrameListenera czyli utworzoną przez nas MoveDevoListener. Kod metody createFrameListener wprowadzamy bezpośrednio za lub przed metodą createScene.

void createFrameListener(void)

mFrameListener= new MoveDemoListener(mWindow, mCamera, mNode, mEntity, mWalkList); mFrameListener->showDebugOverlay(true); mRoot->addFrameListener(mFrameListener);

Tworzymy scenę Przejdźmy do funkcji MyApp::createScene i dodajmy do niej poniższy kod. Najpierw ustawimy światło otoczenia na maksymalne, dzięki czemu będziemy mogli wyraźnie zobaczyć obiekty w scenie.

// Ustawianie domyślnego światła mSceneMgr->setAmbientLight( ColourValue( 1.0f, 1.0f, 1.0f ) );

Następnie dodamy robota. W tym celu stworzymy jednostkę i węzeł sceny, które przeznaczymy dla naszego robota.

// Tworzenie jednostki mEntity = mSceneMgr->createEntity( "Robot", "robot.mesh" );

// Tworzenie węzła sceny mNode = mSceneMgr->getRootSceneNode( )-> createChildSceneNode( "RobotNode", Vector3( 0.0f, 0.0f, 25.0f ) ); mNode->attachObject( mEntity );

Kolejnym krokiem będzie określenie, po których miejscach ma chodzić robot. Dla tych, którzy dotychczas nie mieli do czynienia z STL wyjaśniamy, że obiekt klasy deque implementuje kolejkę dwustronną, czyli taką o dwóch końcach z których można odbierać lub dodawać elementy. Wykorzystamy tylko kilka metod tej klasy. Metody push_front i push_back umożliwiają wstawienie określonych wartości na początek lub na koniec kolejki. Dzięki front i back możemy się dowiedzieć, jakie wartości znajdują się na początku lub na końcu kolejki. Funkcje pop_front i pop_back umożliwiają usunięcie elementu znajdującego się na początku lub na końcu kolejki. Metoda empty zwraca informację, czy kolejka jest pusta. Kod przedstawiony niżej wstawia do kolejki wektory według których będzie się przesuwał nasz robot:

// Tworzenie listy punktów, po których ma się przesuwać robot mWalkList.push_back( Vector3( 550.0f, 0.0f, 50.0f ) ); mWalkList.push_back( Vector3(-100.0f, 0.0f, -200.0f ) );

Teraz umieścimy kilka "węzełków" w scenie, abyśmy mogli ocenić, jak robot będzie przesuwany porównując jego pozycję z ich położeniem na ekranie.

// Tworzymy węzełki, dzięki którym możemy zobaczyć ruch Entity *ent; SceneNode *node;

ent = mSceneMgr->createEntity( "Knot1", "knot.mesh" ); node = mSceneMgr->getRootSceneNode( )->createChildSceneNode( "Knot1Node", Vector3( 0.0f, -10.0f, 25.0f ) ); node->attachObject( ent ); node->setScale( 0.1f, 0.1f, 0.1f );

ent = mSceneMgr->createEntity( "Knot2", "knot.mesh" ); node = mSceneMgr->getRootSceneNode( )->createChildSceneNode( "Knot2Node", Vector3( 550.0f, -10.0f, 50.0f ) ); node->attachObject( ent ); node->setScale( 0.1f, 0.1f, 0.1f );

ent = mSceneMgr->createEntity( "Knot3", "knot.mesh" ); node = mSceneMgr->getRootSceneNode( )->createChildSceneNode( "Knot3Node", Vector3(-100.0f, -10.0f,-200.0f ) ); node->attachObject( ent ); node->setScale( 0.1f, 0.1f, 0.1f );

Pewnie zauważycie, że "węzełki" są opuszczone w dół o 10 jednostek w stosunku do pozycji samego robota. Teraz jeszcze ustawimy odpowiednio kamerę. Przeniesiemy ją w pozycje, z której uzyskamy lepszy widok na całość sceny.

// Ustawiamy kamerę, żeby patrzyła na naszą pracę mCamera->setPosition( 90.0f, 280.0f, 535.0f ); mCamera->pitch( Degree(-30.0f) ); mCamera->yaw( Degree(-15.0f) );

Przekompiluj i uruchom program. Powinniśmy zobaczyć robota stojącego na jednym z "węzełków" oraz dwa dodatkowe "węzełki" tworzące razem z pierwszym coś w rodzaju trójkąta skierowanego wierzchołkiem w prawo. Na razie nic nam się na ekranie samo nie porusza.

Animacja Spróbujemy zatem wprowadzić w naszą scenę nieco ruchu. Animacja w Ogre jest bardzo prosta do oprogramowania. W definicji jednostki zawarte już są pewne wprowadzone, przez projektanta obiektu 3D, stany i animacje. Stany to różne pozycje statyczne z róznie ustawionymi elementów, z których się składa jednostka. Animacje to sekwencje takich stanów i przejść pomiędzy nimi. Definicja "robot.mesh" zawiera w sobie animację marszu i musimy ją tylko wywołać. Aby zobaczyć wszystkie animacje dla siatki 3D możesz pobrać OgreMeshViewer i w nim je zobaczyć. Aby to zrobić pobieramy AnimationState z jednostki i ustawiamy jej opcje. Gdy to wszystko mamy przygotowane po prostu włączamy animację. Przejdźmy do konstruktora MoveDemoListener i dodajmy poniższy kod:

// Konfiguracja animacji mAnimationState = ent->getAnimationState( "Idle" ); mAnimationState->setLoop( true ); mAnimationState->setEnabled( true );

Druga linia pobiera AnimationState naszej jednostki. W trzeciej linie wywołujemy setLoop( true ), która tworzy pętlę animacji. Dla niektórych animacji (np. w takiej, w której coś ginie), animacja nie będzie powtarzać się w kółko, więc powinniśmy tą opcję ustawić na false. Czwarta linia włącza animację. Ale dlaczego animację pobraliśmy z wartością Idle? Otóż nazwa ta określa to domyślną standardową animację czynności dla danej jednostki w momencie gdy nic ona nie wykonuje. W przypadku jednostki ukazującej człowieka byłoby to na przykład oddychanie.

Gdybyś teraz przekompilował i uruchomił program dalej uzyskałbyś statyczną, nieruchomą scenę. A to dlatego, że pętla animacja nie wie nic o upływie czasu. Znajdźmy metodę MoveDemoListener::frameStarted i dodajmy do niej na początku kod:

mAnimationState->addTime( evt.timeSinceLastFrame );

Przekazujemy w ten sposób do animacji co każdą klatkę czas jaki upłynął od poprzedniej. System może wtedy tworzyć kolejne klatki z odpowiednio zmienionym wyglądem jednostki. Skompiluj i uruchom aplikację. Robot będzie stał w miejscu ale będzie też lekko poruszał swoim korpusem. Możesz poeksperymentować z innymi nazwami animacji niż "Idle". W przypadku robota będziesz miał jeszcze możliwośc skorzystania z "Walk" i "Die". Za każdym razem zauważ, że robot swą czynność wykonuje w tym samym miejscu. Zapamiętaj, że animacja zmienia wygląd jednostki lecz nie zmienia jej położenia. Gdybyś się pomylił w nazwie i wybrał nieistniejącą animację danej jednostki, ekran po przejciu w tryb graficzny 3D pozostanie ciemny.

Przenoszenie robota Zajmiemy się teraz ruchem robota z jednego miejsca do drugiego. Zanim zaczniemy wyjaśnimy do czego będą nam potrzebne zmienne utworzone w klasie MoveDemoListener. Użyjemy czterech zmiennych, aby poruszyć naszego robota. Pierwsza zmienna, mDirection, będzie służyła do przechowywania kierunku robota, w którym się porusza. Aktualny cel podróży będziemy przechowywali w zmiennej mDestination. Dystans będziemy przechowywać w mDistance a szybkość przesuwania w mWalkSpeed.

W konstruktorze klasy MoveDemoListener usuńmy wszystkie poprzednie wpisy a ustawmy teraz zmienne na odpowiednie, domyślne wartości. Szybkość chodzenia ustawmy na 35 jednostek na sekundę. Do mDirection przypiszemy wektor zerowy (ZERO) co będzie nam potrzebne do określenia czy robot się porusza czy też nie.

// Ustawianie domyślnych wartości zmiennych mWalkSpeed = 35.0f; mDirection = Vector3::ZERO;

Animacja chodu Sprawmy by robot wyglądał tak jakby chodził. Jak pewnie sobie przypominacie wystarczy w tym celu zmienić obsługiwaną w danej chwili animację zawartą w siatce 3D. Jednak zróbmy to tak by zmiana ta dokonywała się tylko wtedy gdy faktycznie jednostka jest przesuwana na ekranie. Aby sprawdzić czy jest w ruchu będziemy wywoływać funkcję nextLocation. Powinna ona nam zwrócić true jeżeli jeszcze jest jakiś dystans lub kolejny odcinek do przejścia. Jeżeli natomiast dotarliśmy do samego końca powinna zwrócić false. Dodaj ten kod na górze metody MoveDemoListener::frameStarted, przed dwoma istniejącymi tam już liniami.

if ( mDirection == Vector3::ZERO )

if ( nextLocation() )

// Set walking animation mAnimationState = mEntity->getAnimationState( "Walk" ); mAnimationState->setLoop( true ); mAnimationState->setEnabled( true );

Jeśli teraz przekompilujemy i uruchomimy program, zobaczymy robota idącego w miejscu. Jest tak, ponieważ nie oprogramowaliśmy jeszcze przesunięcia jednostki a sam robot jest już ustawiony z kierunkiem ZERO i funkcja MoveDemoListener::nextLocation na razie zawsze zwraca true co powoduje włączenie animacji. W następnym kroku zmienimy nieco funkcję MoveDemoListener::nextLocation co pozwoli nam ominąć ten problem.

Ruch jednostki Skupmy się teraz na ruszeniu robota z miejsca. Będziemy przesuwać jednostkę o pewien mały dystans co każdą klatkę. Dystans ten będzie zależny od czasu jaki upłynął pomiędzy klatkami. Przejdźmy do metody MoveDemoListener::frameStarted. Wstawimy kod który będzie odpowiadał za sytuację w której robot jest już w trakcie przemieszczania (mDirection nie równa się Vector3::ZERO). Poniższy kod dodamy zaraz przed linią zawierającą instrukcję return. Będzie on tworzyć dalszą część instrukcji if.

else

Real move = mWalkSpeed * evt.timeSinceLastFrame; mDistance -= move;

Musimy skontrolować czy nasz robot nie poszedł za daleko. Nie możemy sprawdzać czy już znajduje się w pozycji celu gdyż w czasie trwania jednej ramki może się zdarzyć, że przejdzie trochę dalej, a w przestrzeni trójwymiarowej nie stwierdzimy czy faktycznie jest za czy przed celem. Zastosujemy zmienną mDistance która stanie się ujemna po przejściu celu. Gdy stanie się ujemna musimy cofnąć się "skokowo" do punktu w kierunku którego podążaliśmy. Ustawimy wówczas także mDirection na wektor zerowy. Jeśli wywoływana w następnej części metoda nextLocation nie zmieni mDirection, wtedy nie musimy już dalej się poruszać, ponieważ dotarliśmy do celu.

if (mDistance setPosition( mDestination ); mDirection = Vector3::ZERO;

Kiedy już dotarliśmy do punktu określającego koniec prostej musimy sprawdzić czy to już koniec czy też mamy do prześcia kolejny odcinek. Po sprawdzeniu ustawiamy odpowiednią animację.

// Ta animacja ustawiana jest jeśli już dalej nie mamy gdzie iść. if (! nextLocation( ) )

// Set Idle animation mAnimationState = mEntity->getAnimationState( "Idle" ); mAnimationState->setLoop( true ); mAnimationState->setEnabled( true );

else

// Tu później wstawimy kod obracjący robota