基礎教程五(緩衝輸入)

來源:互聯網
上載者:User
先決條件

本教程假定你已經擁有了c++程式設計的知識,並且已經安裝和編譯了一個Ogre的應用程式(如果你在設定你的應用程式中有困難,請參考this guide獲得更詳細的編譯步驟)。這個教程同時也是建立在上一章基礎上的,因此預設你已經瞭解了上個教程的內容。

[編輯]介紹

在這一課裡,你將學會OIS的帶緩衝的輸入, 這不同於上節課中無緩衝的輸入。而且在這節課中,當滑鼠鍵盤的事件發生時,我們會立即處理它,不會像上節課那樣每一幀只處理一次。請注意這裡只是對緩衝輸入的一個簡單介紹,而不是完整的如何使用OIS的教程。若想瞭解更多內容,請查閱相關的OIS使用教程。

你可以找到這節課的原始碼。在你學習本課的時候,你應該逐個地將這些代碼添加到你自己的工程裡去,然後構建並觀察結果。

[編輯]從這開始

本課是基於上一課的,但我們將改變輸入的方式。既然功能基本上是相同的,我們就使用上次的TutorialApplication類,但我們會從TutorialFrameListener開始。用你喜歡的編譯器建立一個工程,再添加一個包含如下代碼的源檔案:

#include "ExampleApplication.h" class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener{public:    TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr)  : ExampleFrameListener(win, cam, true, true)    {    }    bool frameStarted(const FrameEvent &evt)    {         if(mMouse)            mMouse->capture();        if(mKeyboard)             mKeyboard->capture();        return mContinue;    }     // MouseListener    bool mouseMoved(const OIS::MouseEvent &e) { return true; }    bool mousePressed(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; }    bool mouseReleased(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; }     // KeyListener    bool keyPressed(const OIS::KeyEvent &e) { return true; }    bool keyReleased(const OIS::KeyEvent &e) { return true; }protected:    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     bool mContinue;        // Whether to continue rendering or not    Vector3 mDirection;     // Value to move in the correct direction};     class TutorialApplication : public ExampleApplication{public:    void createCamera(void)    {         // create camera, but leave at default position        mCamera = mSceneMgr->createCamera("PlayerCam");         mCamera->setNearClipDistance(5);    }     void createScene(void)    {        mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25));        // add the ninja        Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");        SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");        node->attachObject(ent);           // create the light        Light *light = mSceneMgr->createLight("Light1");        light->setType(Light::LT_POINT);        light->setPosition(Vector3(250, 150, 250));        light->setDiffuseColour(ColourValue::White);        light->setSpecularColour(ColourValue::White);         // Create the scene node        node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400));         // Make it look towards the ninja        node->yaw(Degree(-45));        // Create the pitch node        node = node->createChildSceneNode("PitchNode1");        node->attachObject(mCamera);         // create the second camera node/pitch node        node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400));        node = node->createChildSceneNode("PitchNode2");    }    void createFrameListener(void)    {        // Create the FrameListener        mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr);        mRoot->addFrameListener(mFrameListener);         // Show the frame stats overlay        mFrameListener->showDebugOverlay(true);    }};#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)#elseint 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;}

如果你是在Windows下使用OgreSDK的,請確定添加“"[OgreSDK_DIRECTORY]/samples/include”目錄到這個工程(ExampleApplication.h檔案所在的位置)除了標準包含以外。如果使用的是Ogre的原始碼包,這個檔案應該在“"[OgreSource_DIRECTORY]/Samples/Common/include”目錄中。不要試著去運行程式,因為我們還沒有定義鍵盤的行為。如果你有問題,請查看這個Wiki頁獲得設定你編譯器的資訊,如果仍然有問題請試著去看看協助欄。

注意:如果你沒有把你的工程設定成使用Ansi C++ ,並收到關於MessageBox的錯誤資訊,你可能需要把對MessageBox的引用改成MessageBoxA。

程式的控制與上一課的相同。

[編輯]簡單的緩衝輸入

[編輯]介紹

在上一次課裡,我們使用的是無緩衝的輸入,也就是說,在每一幀裡我們查詢OIS::Keyboard和OIS::Mouse執行個體的狀態,以判斷它們是否被按下。而緩衝輸入使用了一個listener介面,以便在事件發生時通知你的程式。比如,當一個鍵被按下時,會觸發一個 KeyListener::keyPressed 事件,而當這個鍵被釋放(不再按下)時,KeyListener::keyReleased 事件被觸發給所有登入的KeyListener類。這些能用在追蹤按鍵的時間,或判斷按鍵在上一幀中是否沒有被按下。

通過OIS::JoystickListener 介面,OIS也支援無緩衝的操縱杆事件,但在本課我們不會涉及到。

關於OIS的監聽系統有一點要注意的是,對於每一個Keyboard,Mouse,Joystick對象只能有一個監聽器。這樣是為了簡單(也為了速度)。多次調用setEventCallback函數(後面會講到)的結果是只有最後一次註冊的監聽器才得到事件訊息。如果你有多個對象需要獲得Key,Mouse,或Joystick事件,你只有自己寫一個訊息分發。還有,千萬記得在frameStarted方法裡調用Keyboard::capture和Mouse::capture。OIS不會使用線程(或其它玩意兒)來確定鍵盤滑鼠的狀態,所以你必須指明什麼時候去擷取輸入。

[編輯]鍵盤監聽介面

OIS的KeyListener介面提供了兩個純虛函數。第一個是keyPressed函數(每次按下某個鍵時調用它),還一個是keyReleased(每次離開某個鍵時調用它)。傳入這些函數的參數是一個KeyEvent,它包含被按下/釋放的按鍵的鍵碼。

[編輯]滑鼠監聽介面

MouseListener介面比KeyListener介面要稍微複雜一些。它包含查看何時滑鼠鍵被按下/釋放的函數: MouseListener::mousePressed 和 MouseListener::mouseReleased. 它還包含一個mouseMoved函數,當滑鼠移動時調用它。這些函數都接收一個MouseEvent對象,在"state"變數裡儲存著當前滑鼠的狀態。

需要注意的是,MouseState對象即包含了滑鼠移動的相對XY座標(即,從上一次調用MouseListener::mouseMoved開始,它所移動的距離),還包含了絕對XY座標(即,螢幕上的準確位置)。

[編輯]代碼

[編輯]TutorialFrameListener的建構函式

在我們開始修改TutorialFrameListener之前,請注意我們對TutorialFrameListener類做了兩處大的改變:

class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener 我們繼承了OIS的MouseListener和KeyListener類,這樣我們才能從它們那裡接收事件。注意,OIS的MouseListener處理滑鼠按鍵事件,也處理滑鼠移動事件。

同樣,ExampleFrameListener的建構函式也有變化:

  : ExampleFrameListener(win, cam, true, true)

這個"true, true"參數指明了我們將要使用帶緩衝的鍵盤滑鼠輸入。我們接下面會深入介紹如何手動地設定OIS,以及Ogre的其它部分。

[編輯]變數

上一課的變數在這裡有些改變。我移除了mToggle和mMouseDown變數(再也用不到了)。我還添加了一些其它的:

   Real mRotate;          // 旋轉常量   Real mMove;            // 運動常量   SceneManager *mSceneMgr;   // 當前的情境管理器   SceneNode *mCamNode;   // 當前攝像機附著的情境節點   bool mContinue;        // 是否要繼續渲染   Vector3 mDirection;     // 指向正確的移動方向

mRotate, mMove, mSceneMgr, 以及mCamNode與上一課的相同(雖然我們會改變mRotate的值,因為它有不同的用處)。 mContinue變數是frameStarted方法的傳回值。當mContinue為false的時候,程式退出。mDirection變數指定了在每一個幀裡我們如何移動攝像機節點。

[編輯]TutorialFrameListener建構函式

在建構函式裡,我們像在上一課那樣初始化一些變數,並把mContinue設成true。添加如下代碼到TutorialFrameListener的建構函式裡:

       // Populate the camera and scene manager containers       mCamNode = cam->getParentSceneNode();       mSceneMgr = sceneMgr;       // 設定旋轉和移動速度       mRotate = 0.13;       mMove = 250;       // 繼續渲染       mContinue = true;

在ExampleFrameListener的建構函式裡已經取得了OIS的mMouse和mKeyboard對象。我們調用這些輸入對象的setEventCallback方法,把TutorialFrameListener註冊成一個監聽器。

       mMouse->setEventCallback(this);       mKeyboard->setEventCallback(this);

最後,我們還要把mDirection初始化成零向量(因為我們最開始不需要它動):

       mDirection = Vector3::ZERO;

[編輯]鍵盤綁定

在我們深入之前,我們應該設定Escape鍵用來退出程式。找到TutorialFrameListener::keyPressed方法,每當鍵盤上一個鍵被按下時,都會調用這個方法並傳入一個KeyEvent對象。我們能夠通過這個對象的"key"變數來擷取按鍵的鍵碼(KC_*)。基於這個值,我們構造一個switch,為綁定所有程式裡用到的按鈕。

  bool keyPressed(const OIS::KeyEvent &e)  {      switch (e.key)      {          case OIS::KC_ESCAPE:               mContinue = false;              break;      }      return true;  }

我們繼續之前請保證以上都能編譯和運行。

我們需要在switch語句裡為其它按鈕做綁定。首先我們要讓使用者按1、2鍵進行視口的切換。

代碼基本上與上節課相同(需要包括進switch語句),除了不需要處理mToggle變數:

      case OIS::KC_1:          mCamera->getParentSceneNode()->detachObject(mCamera);          mCamNode = mSceneMgr->getSceneNode("CamNode1");          mCamNode->attachObject(mCamera);          break;      case OIS::KC_2:          mCamera->getParentSceneNode()->detachObject(mCamera);          mCamNode = mSceneMgr->getSceneNode("CamNode2");          mCamNode->attachObject(mCamera);          break;

瞧!這比用臨時變數來儲存按鍵時間要乾淨多了。

接下來我們要添加鍵盤移動。每次使用者按下移動按鍵,我們都要朝正確的方向加上或者減去mMove:

      case OIS::KC_UP:      case OIS::KC_W:          mDirection.z -= mMove;          break;      case OIS::KC_DOWN:      case OIS::KC_S:          mDirection.z += mMove;          break;      case OIS::KC_LEFT:      case OIS::KC_A:          mDirection.x -= mMove;          break;      case OIS::KC_RIGHT:      case OIS::KC_D:          mDirection.x += mMove;          break;      case OIS::KC_PGDOWN:      case OIS::KC_E:          mDirection.y -= mMove;          break;      case OIS::KC_PGUP:      case OIS::KC_Q:          mDirection.y += mMove;          break;

當按鍵被釋放時,我們要立即取消mDirection向量上的移動。找到keyReleased方法,添加如下代碼:

      switch (e.key)      {      case OIS::KC_UP:      case OIS::KC_W:          mDirection.z += mMove;          break;      case OIS::KC_DOWN:      case OIS::KC_S:          mDirection.z -= mMove;          break;      case OIS::KC_LEFT:      case OIS::KC_A:          mDirection.x += mMove;          break;      case OIS::KC_RIGHT:      case OIS::KC_D:          mDirection.x -= mMove;          break;      case OIS::KC_PGDOWN:      case OIS::KC_E:          mDirection.y += mMove;          break;      case OIS::KC_PGUP:      case OIS::KC_Q:          mDirection.y -= mMove;          break;      } // switch      return true;

好了,我們能根據按鍵輸入對mDirection進行更新了,我們還需要讓它真正移動起來。下面的代碼與上節課是一樣的,添加到frameStarted函數裡:

       mCamNode->translate(mDirection * evt.timeSinceLastFrame, Node::TS_LOCAL);

編譯並運行程式。我們現在已經有了帶緩衝的輸入來控制運動!

[編輯]滑鼠綁定

現在我們已經完成了鍵盤綁定,接下來輪到滑鼠了。我們從點擊滑鼠左鍵來控制燈的開關開始。找到mousePressed函數並看看它的參數。用OIS,我們可以訪問MouseEvent和MouseButtonID。我們用MouseButtonID作為switch條件,來確定按下的是哪個按鈕。用下面的代碼替換掉mousePressed函數裡的:

      Light *light = mSceneMgr->getLight("Light1");      switch (id)      {      case OIS::MB_Left:          light->setVisible(! light->isVisible());          break;      }      return true;

編譯並運行。 噢耶! 成功了,剩下來的事情就是綁定滑鼠右鍵來進入滑鼠觀察模式。每當滑鼠移動時我們都檢查右鍵是否按下。如果是,我們基於相對運動來轉動攝像機。通過傳入函數的MouseEvent對象,我們能擷取相對運動。它包含一個"state"變數,裡面有滑鼠的狀態(是關於滑鼠的詳細資料)。MouseState::buttonDown告訴我們是否一個特定的按鈕被按下,而“X”和“Y”變數告訴我們滑鼠的相對運動。找到mouseMoved方法,用以下代碼替換掉原來的:

      if (e.state.buttonDown(OIS::MB_Right))      {          mCamNode->yaw(Degree(-mRotate * e.state.X.rel), Node::TS_WORLD);          mCamNode->pitch(Degree(-mRotate * e.state.Y.rel), Node::TS_LOCAL);      }      return true;

編譯並運行程式。當滑鼠右鍵被按下時,攝像機就以自由視角的模式工作。

[編輯]其他輸入系統

OIS是非常棒的,應該滿足你的應用程式的大部分需求。話說回來,你也有其它的選擇。有的視窗系統像wxWidgets也許正是你想要的,人們已經成功地把它整合進了Ogre。如果你不介意你的應用程式是特定平台的,你也可以使用標準的Windows訊息系統,或者從Linux GUI工具集中選擇一個。

你也可以試一試SDL,它是一個跨平台的帶視窗輸入系統,並支援搖杆/手柄輸入。我不能指導你用Ogre如何設定wx/gtk/qt這些(因為我自己也沒搞過-_-),但我有許多將SDL的搖杆/手柄輸入整合進Ogre的成功經驗。為了使SDL的操縱杆系統生效,通過調用SDL_Init和SDL_Quit,把你的應用程式套件組合裹起來(放在你的程式main函數裡,或放在你的應用對象裡):

      SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE);      SDL_JoystickEventState(SDL_ENABLE);      app.go();      SDL_Quit();

為了設定Joystick,調用SDL_JoystickOpen並傳入操縱杆編號(你能通過0、1、2...來指定多個操縱杆):

  SDL_Joystick* mJoystick;  mJoystick = SDL_JoystickOpen(0);  if ( mJoystick == NULL )  ; // error handling

如果JoystickOpen返回NULL,表示開啟joystick有問題。這基本上意味著你請求的搖杆不存在。使用SDL_NumJoysticks來確定串連在系統上的搖杆數量。當你結束之後,需要關閉搖杆:

  SDL_JoystickClose(mJoystick);

為了使用搖杆,調用JoystickGetButton和JoystickGetAxis。我個人使用的是PS2的手柄,所以我有四個軸向和12個按鈕來玩。這是我的操縱代碼:

      SDL_JoystickUpdate();      mTrans.z += evt.timeSinceLastFrame * mMoveAmount * SDL_JoystickGetAxis(mJoystick, 1) / 32767;      mTrans.x += evt.timeSinceLastFrame * mMoveAmount * SDL_JoystickGetAxis(mJoystick, 0) / 32767;      xRot -= evt.timeSinceLastFrame * mRotAmount * SDL_JoystickGetAxis(mJoystick, 3) / 32767;      yRot -= evt.timeSinceLastFrame * mRotAmount * SDL_JoystickGetAxis(mJoystick, 2) / 32767;

mTrans在後面要傳入到攝像機的SceneNode::translate方法,xRot傳入到SceneNode::yaw,而yRot傳入到SceneNode::pitch。注意,SDL_JoystickGetAxis返回一個從-32767到32767的值,所以我得把它縮小到-1到1的範圍。這應該可以讓你開始使用SDL搖杆輸入了。若你想查看更多關於這個領域的資訊,SDL joystick的標頭檔是最好的文檔。

如果你想要認真地在你的程式時使用SDL,你應該查閱標準SDL文檔。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.