16 grudnia 2009 Tutorial 4: Ruch



Ten tutorial pokazuje jak poruszać i animować SceneNody. Pokazuje podstawowe koncepcje animacji SceneNodów i manualny ruch nodów używających klawiatury. Zademonstrujemy ruch niezalezny od wskaźnika ramek, co oznacza poruszanie przez ilość zależną od trwania ostatniego uruchomienia pętli Irrlicht.

Przykład 19. Myszka I Dżojstik pokazuje jak radzić sobie z tego rodzaju danymi wejściowymi.

Jak zawsze dołączamy pliki nagłówkowe, używamy przesteni nazw irr i mówimy linkerowi żeby połączył z plikiem .lib

           
            #ifdef _MSC_VER
// We'll also define this to stop MSVC complaining about sprintf().
#define _CRT_SECURE_NO_WARNINGS
#pragma comment(lib, "Irrlicht.lib")
#endif

#include <irrlicht.h>
#include <iostream>

using namespace irr;

Aby otrzymać zdażenia z myszki czy klawiatury lub wydarzenia GUI jak np. "klawisz OK został wciśnięty", potrzebujemy obiektu, który pochodzi z obiektu irr::IEventReceiver. Istnieje tylko jedna metoda aby to ominąć: irr::IEventReceiver::OnEvent(). Ta metoda będzie wzywana przez silnik kiedy tylko to wydarzenie się stanie. To, co chcemy tak naprawde wiedzieć to czy przycisk jest trzymany i przez to będziemy pamietać aktualny stan każdego przycisku.

class MyEventReceiver : public IEventReceiver
{
public:
        // This is the one method that we have to implement
        virtual bool OnEvent(const SEvent& event)
        {
                // Remember whether each key is down or up
                if (event.EventType == irr::EET_KEY_INPUT_EVENT)
                        KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;

                return false;
        }

        // This is used to check whether a key is being held down
        virtual bool IsKeyDown(EKEY_CODE keyCode) const
        {
                return KeyIsDown[keyCode];
        }
        
        MyEventReceiver()
        {
                for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
                        KeyIsDown[i] = false;
        }

private:
        // We use this array to store the current state of each key
        bool KeyIsDown[KEY_KEY_CODES_COUNT];
};

Event receiver sprawdza czy któryś nacisniętych klawiszy jest gotowy, własciwe odpowiedzi będą robione wewnątrz pętli rendera zaraz przed rysowaniem scen. Stwórzmy więc irr::IrrlichtDevice i scene node, który chcemy poruszać. Tworzymy także inne dodatkowe scene nody aby pokazać, że istnieją inne możliwości aby poruszać i animowac scene nody.
int main()
{
        // let user select driver type

        video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;

        printf("Please select the driver you want for this example:\n"\
                " (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
                " (d) Software Renderer\n (e) Burning's Software Renderer\n"\
                " (f) NullDevice\n (otherKey) exit\n\n");

        char i;
        std::cin >> i;

        switch(i)
        {
                case 'a': driverType = video::EDT_DIRECT3D9;break;
                case 'b': driverType = video::EDT_DIRECT3D8;break;
                case 'c': driverType = video::EDT_OPENGL;   break;
                case 'd': driverType = video::EDT_SOFTWARE; break;
                case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
                case 'f': driverType = video::EDT_NULL;     break;
                default: return 0;
        }       

        // create device
        MyEventReceiver receiver;

        IrrlichtDevice* device = createDevice(driverType,
                        core::dimension2d(640, 480), 16, false, false, false, &receiver);

        if (device == 0)
                return 1; // could not create selected driver.

        video::IVideoDriver* driver = device->getVideoDriver();
        scene::ISceneManager* smgr = device->getSceneManager();

Utwórz nod, który będzie poruszany klawiszami WSAD. Tworzymy sphere node, który jest wbudowaną elementarną geometrią. Umiejscawiamy nod na (0,0,30) i przydzielamy teksturę, żeby pozwolić mu wyglądać trochę bardziej interesująco. Ponieważ nie mamy zadnych dynamicznych świateł w tej scenie, wyłączamy oświetlenie dla każdego modelu (w innym wypadku modele będą czarne).
        scene::ISceneNode * node = smgr->addSphereSceneNode();
        if (node)
        {
                node->setPosition(core::vector3df(0,0,30));
                node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
                node->setMaterialFlag(video::EMF_LIGHTING, false);
        }

Teraz tworzymy kolejny nod, ruchomy dzieki użyciu animatora scene node. Animatory scene node modyfikują scene nody i mogą być dołączone do każdego scene noda jak mesh scene node, billboardy, światła oraz również scene nody kamery. Animatory scene node nie tylko modyfikują pozycję scene nodów ale mogą również np. animować tekstury obiektu. Tworzymy cube scene node i dodajemy do niego 'fly circle' animator scene node (latające koło), umożliwiając temu nodowi latanie wokół naszej sphere scene node.
        scene::ISceneNode* n = smgr->addCubeSceneNode();

        if (n)
        {
                n->setMaterialTexture(0, driver->getTexture("../../media/t351sml.jpg"));
                n->setMaterialFlag(video::EMF_LIGHTING, false);
                scene::ISceneNodeAnimator* anim =
                        smgr->createFlyCircleAnimator(core::vector3df(0,0,30), 20.0f);
                if (anim)
                {
                        n->addAnimator(anim);
                        anim->drop();
                }
        }

Ostatni scene nod musimy dodać aby pokazać możliwości animatorów scene node jakim jest model md2, który używa animatora 'fly straight' aby uruchomić animacje pomiędzy punktami
        scene::IAnimatedMeshSceneNode* anms =
                smgr->addAnimatedMeshSceneNode(smgr->getMesh("../../media/ninja.b3d"));

        if (anms)
        {
                scene::ISceneNodeAnimator* anim =
                        smgr->createFlyStraightAnimator(core::vector3df(100,0,60),
                        core::vector3df(-100,0,60), 3500, true);
                if (anim)
                {
                        anms->addAnimator(anim);
                        anim->drop();
                }

Aby nasz model wyglądał dobrze wyłączamy światła, ustawiamy ramki, pomiędzy którymi animacja bedzie się zapętlać, obracamy model o 180 stopni i dostosowujemy szybkość animacji i teksturę. Aby ustawić prawidłową animację (ramki i szybkość), bedziemy mogli tylko wywołać "anms->setMD2Animation(scene::EMAT_RUN)" do uruchomienia animacji zamiast "setFrameLoop" i "setAnimationSpeed", lecz działa to tylko z animacjami MD2. W ten sposób wiesz już jak uruchomić inne animacje. Ale dobra rada nie jest do używania ustalonych liczb ramek...
                anms->setMaterialFlag(video::EMF_LIGHTING, false);

                anms->setFrameLoop(0, 13);
                anms->setAnimationSpeed(15);
//              anms->setMD2Animation(scene::EMAT_RUN);

                anms->setScale(core::vector3df(2.f,2.f,2.f));
                anms->setRotation(core::vector3df(0,-90,0));
//              anms->setMaterialTexture(0, driver->getTexture("../../media/sydney.bmp"));

        }


Aby móc patrzeć i poruszać się w tej scenie tworzymy widok z pierwszej osoby i czynimy kursor myszki niewidocznym.
        smgr->addCameraSceneNodeFPS();
        device->getCursorControl()->setVisible(false);
        

Dodajemy kolorowe logo irrlicht.
        device->getGUIEnvironment()->addImage(
                driver->getTexture("../../media/irrlichtlogoalpha2.tga"),
                core::position2d(10,20));

        gui::IGUIStaticText* diagnostics = device->getGUIEnvironment()->addStaticText(
                L"", core::rect(10, 10, 400, 20));
        diagnostics->setOverrideColor(video::SColor(255, 255, 255, 0));

Zrobiliśmy już wszystko, więc narysujmy to. Piszemy także aktualne ramki na sekunde i nazwę sterownika do nagłówka okna.
        int lastFPS = -1;

        // In order to do framerate independent movement, we have to know
        // how long it was since the last frame
        u32 then = device->getTimer()->getTime();

        // This is the movemen speed in units per second.
        const f32 MOVEMENT_SPEED = 5.f;

        while(device->run())
        {
                // Work out a frame delta time.
                const u32 now = device->getTimer()->getTime();
                const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds
                then = now;

Sprawdź czy klawisze W, S, A albo D są przyciskane i przesuń odpowiednio sphere node.
                core::vector3df nodePosition = node->getPosition();

                if(receiver.IsKeyDown(irr::KEY_KEY_W))
                        nodePosition.Y += MOVEMENT_SPEED * frameDeltaTime;
                else if(receiver.IsKeyDown(irr::KEY_KEY_S))
                        nodePosition.Y -= MOVEMENT_SPEED * frameDeltaTime;

                if(receiver.IsKeyDown(irr::KEY_KEY_A))
                        nodePosition.X -= MOVEMENT_SPEED * frameDeltaTime;
                else if(receiver.IsKeyDown(irr::KEY_KEY_D))
                        nodePosition.X += MOVEMENT_SPEED * frameDeltaTime;

                node->setPosition(nodePosition);

                driver->beginScene(true, true, video::SColor(255,113,113,133));

                smgr->drawAll(); // draw the 3d scene
                device->getGUIEnvironment()->drawAll(); // draw the gui environment (the logo)

                driver->endScene();

                int fps = driver->getFPS();

                if (lastFPS != fps)
                {
                        core::stringw tmp(L"Movement Example - Irrlicht Engine [");
                        tmp += driver->getName();
                        tmp += L"] fps: ";
                        tmp += fps;

                        device->setWindowCaption(tmp.c_str());
                        lastFPS = fps;
                }
        }

Na końcu, usuń urzadzenie Irrlicht.
         device->drop();
        
        return 0;
}

I to by było na tyle. Skompiluj i baw się programem.

PiotrSOG