?? jichu4.txt
字號:
// Create the scene node
node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400));
node->yaw(Degree(-45));
node->attachObject(mCamera);
// create the second camera node
node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400));
現(xiàn)在我們在TutorialApplication類里做得差不多了。再到TutorialFrameListener里去...
[編輯]幀監(jiān)聽指南
[編輯]變量
在我們走得更遠之前,還是先介紹一下TutorialFrameListener類里的一些變量:
bool mMouseDown; // 鼠標左鍵是否在上一幀被按下
Real mToggle; // 直到下一次觸發(fā)的時間
Real mRotate; // 滾動常量
Real mMove; // 移動常量
SceneManager *mSceneMgr; // 當前場景管理器
SceneNode *mCamNode; // 當前攝像機所附在的場景節(jié)點
mSceneMgr擁有一個目前SceneManager的指針,mCamNode擁有目前SceneNode并且綁定著攝像機。mRotate和mMove是旋轉(zhuǎn)和移動的變量。如果你想移動或旋轉(zhuǎn)的更快或更慢,改變這兩個變量的大小就可以了。 另外兩個變量(mToggle和mMouseDown)控制我們的輸入。我們將在本章中使用“非緩沖”(unbuffered)鼠標和鍵盤輸入(“緩沖”(buffered)輸入將是下一章的議題。這意味著我們將在幀監(jiān)聽器查詢鼠標和鍵盤狀態(tài)時調(diào)用方法。
當我們試圖用鍵盤來改變屏幕中物體的狀態(tài)時,會遇到一些有趣的問題。如果知道一個鍵正被按下,我們會去處理它,但到了下一幀怎么辦?我們還會看見它被按下,然后再重復地做操作嗎?在一些情況(比如用方向鍵來移動),我們是這樣做的。然而,我們想通過“T”鍵來控制燈的開關(guān)時,在第一幀里按下T鍵,撥動了燈的開關(guān),但在下一幀T鍵仍然被按著,所以燈又打開了...如此返復,直到按鍵被釋放。我們必須在幀與幀之間追蹤這些按鍵狀態(tài),才能避免這個問題。我將介紹兩種不同的方法來解決它。
mMouseDown用來追蹤鼠標在上一幀里是否也被按下(所以如果mMouseDown為真,在鼠標釋放之前我們不會做同樣的操作)。mToggle指定了直到我們可以執(zhí)行下一個操作的時間。即,當一個按鍵被按下去了,在mToggle指明的這段時間里不允許有其它動作發(fā)生。
[編輯]構(gòu)造器
關(guān)于這個構(gòu)造器首先需要注意的是,我們要調(diào)用ExampleFrameListener的構(gòu)造器: 首先我們要繼承ExampleFrameListener的constructor。
: ExampleFrameListener(win, cam, false, false)
一個需要注意的是,第三和第四個參數(shù)要置為false。第三個參數(shù)指明是否使用帶緩沖的鍵盤輸入,第四個參數(shù)指明是否使用帶緩沖的鼠標輸入(我們在本課都不使用)。
在TutorialFrameListener的構(gòu)造器里, 我們把所有的變量設(shè)置為默認值:
// 鍵盤和鼠標狀態(tài)追蹤
mMouseDown = false;
mToggle = 0.0;
// Populate the camera and scene manager containers
mCamNode = cam->getParentSceneNode();
mSceneMgr = sceneMgr;
// 設(shè)置旋轉(zhuǎn)和移動速度
mRotate = 0.13;
mMove = 250;
好了。mCamNode變量被初始化成攝像機的任何當前父親。
[編輯]幀啟動方法
現(xiàn)在,我們到了教程最核心的部分:在每一幀里執(zhí)行動作。目前我們的frameStarted方法里有如下代碼:
return ExampleFrameListener::frameStarted(evt);
這塊代碼非常重要。ExampleFrameListener::frameStarted方法定義了許多行為(像所有的按鍵綁定、所有的攝像機移動等)。清空TutorialFrameListener::frameStarted方法。
開放輸入系統(tǒng)(OIS)提供了三個主要的類來獲得輸入:Keyboard, Mouse, 和 Joystick 。在教程里,我們只有涉及如何使用Keyboard和Mouse對象。倘若你對在Ogre里使用搖桿感興趣,請查閱Joystick類。
我們使用無緩沖輸入時,要做的第一件事情就是獲取當前鼠標鍵盤的狀態(tài)。為了達到目的,我們調(diào)用Mouse和Keyboard對象的capture方法。示例框架已經(jīng)幫我們創(chuàng)建了這些對象,分別在mMouse和mKeyboard變量里。將以下代碼添加到目前還是空的TutorialFrameListener::frameStarted成員方法里。
mMouse->capture();
mKeyboard->capture();
接下來,我們要保證當Escape鍵被按下時程序退出。我們通過調(diào)用InputReader的isKeyDown方法并指定一個KeyCode,來檢查一個按鈕是否被按下。當Escape鍵被按下時,我們只要返回false到程序末尾。
if(mKeyboard->isKeyDown(OIS::KC_ESCAPE))
return false;
為了能繼續(xù)渲染,frameStarted方法必須返回一個正的布爾值。我們將在方法最后加上這么一行。
return true;
所有我們將要討論的代碼都在最后的這一行"return true"之上。
我們使用FrameListener來做的第一個事情就是,讓鼠標左鍵來控制燈的開關(guān)。通過調(diào)用InputReader的getMouseButton方法,并傳入我們要查詢的按鈕,我們能夠檢查這個按鈕是否被按下。通常0是鼠標左鍵,1是右鍵,2是中鍵。在某些系統(tǒng)里1是中鍵、2是右鍵。如果按鍵不能很好的工作,可嘗試這樣設(shè)置:
bool currMouse = mMouse->getMouseState().buttonDown(OIS::MB_Left);
如果鼠標被按下,currMouse變量設(shè)為true。現(xiàn)在我們撥動燈開關(guān),依賴于currMouse是否為true,同時在上一幀中鼠標沒有被按下(因為我們只想每次按下鼠標時,只讓燈開關(guān)被撥動一次)。同時注意Light類里的setVisible方法決定了這個對象實際上是否發(fā)光:
if (currMouse && ! mMouseDown)
{
Light *light = mSceneMgr->getLight("Light1");
light->setVisible(! light->isVisible());
} // if
現(xiàn)在,我們把mMouseDown的值設(shè)置成與currMouse變量相同。下一幀里,它會告訴我們上次是否按下了鼠標按鍵。
mMouseDown = currMouse;
編譯運行程序。現(xiàn)在可以左擊鼠標控制燈的開關(guān)!注意,因為我們不再調(diào)用 ExampleFrameListener的frameStarted方法,我們不能轉(zhuǎn)動攝像頭(目前)。
這種保存上一次鼠標狀態(tài)的方法非常好用,因為我們知道我們已經(jīng)對為鼠標狀態(tài)做了動作。
這樣做的缺點就是你要為每一個被綁定動作的按鍵設(shè)置一個布爾變量。一種能避免這種情況的方法是,跟蹤上一次點擊任何按鍵的時刻,然后只允許經(jīng)過了一段時間之后才觸發(fā)事件。我們用mToggle變量來跟蹤。如果mToggle大于0,我們就不執(zhí)行任何動作,如果mToggle小于0,我們才執(zhí)行動作。我們將用這個方法來進行下面兩個按鍵的綁定。
首先我們要做的是拿mToggle變量減去從上一幀到現(xiàn)在所經(jīng)歷的時間。
mToggle -= evt.timeSinceLastFrame;
現(xiàn)在我們更新了mToggle,我們能操作它了。接下來我們將“1”鍵綁定為攝像機附在第一個場景節(jié)點上。在這之前,我們檢查mToggle變量以保證它是小于0的:
if ((mToggle < 0.0f ) && mKeyboard->isKeyDown(OIS::KC_1))
{
現(xiàn)在我們需要設(shè)置mToggle變量,使得在下一個動作發(fā)生時至少相隔一秒鐘:
mToggle = 0.5f;
接下來,我們將攝像機從當前附屬的節(jié)點取下來,然后讓mCamNode變量含有"CamNode1"節(jié)點,再將攝像機放上去。
mCamera->getParentSceneNode()->detachObject(mCamera);
mCamNode = mSceneMgr->getSceneNode("CamNode1");
mCamNode->attachObject(mCamera);
}
當按下2鍵時,對于CamNode2我們也是這么干的。除了把1換成2,if改成else if,其它代碼都是相同的:
else if ((mToggle < 0.0f) && mKeyboard->isKeyDown(OIS::KC_2))
{
mToggle = 0.5f;
mCamera->getParentSceneNode()->detachObject(mCamera);
mCamNode = mSceneMgr->getSceneNode("CamNode2");
mCamNode->attachObject(mCamera);
}
編譯并運行。我們通過按1、2鍵,能夠轉(zhuǎn)換攝像機的視口。
我們的下一個目標就是,當用戶按下方向鍵或W、A、S、D鍵時,進行mCamNode的平移。
與上面的代碼不同,我們不必追蹤上一次移動攝像機的時刻,因為在每一幀按鍵被按下時我們都要進行平移。這樣一來我們的代碼會相對簡單,首先我們來創(chuàng)建一個Vector3用于保存平移的方位:
Vector3 transVector = Vector3::ZERO;
好的,當按下W鍵或者上箭頭時,我們希望前后移動(也就是Z軸,記住Z軸負的方向是指向屏幕里面的):
if (mKeyboard->isKeyDown(OIS::KC_UP) || mKeyboard->isKeyDown(OIS::KC_W))
transVector.z -= mMove;
對于S鍵和下箭頭鍵,我們基本上也是這么做的,只不過方向是往正Z軸:
if (mKeyboard->isKeyDown(OIS::KC_DOWN) || mKeyboard->isKeyDown(OIS::KC_S))
transVector.z += mMove;
對于左右方向移動的,我們是在X的正負方向進行的:
if (mKeyboard->isKeyDown(OIS::KC_LEFT) || mKeyboard->isKeyDown(OIS::KC_A))
transVector.x -= mMove;
if (mKeyboard->isKeyDown(OIS::KC_RIGHT) || mKeyboard->isKeyDown(OIS::KC_D))
transVector.x += mMove;
最后,我們還想沿著Y軸進行上下移動。我個人喜歡用E鍵或者PageDown鍵進行向下移動,用Q鍵或者PageUp鍵進行向上移動:
if (mKeyboard->isKeyDown(OIS::KC_PGUP) || mKeyboard->isKeyDown(OIS::KC_Q))
transVector.y += mMove;
if (mKeyboard->isKeyDown(OIS::KC_PGDOWN) || mKeyboard->isKeyDown(OIS::KC_E))
transVector.y -= mMove;
好了,我們的transVector變量保存了作用于攝像機場景節(jié)點的平移。這樣做的第一個缺點是如果你旋轉(zhuǎn)了場景節(jié)點,再作平移的時候我們的x、y、z坐標就不對了。為了修正它,我們必須把作用在場景節(jié)點的旋轉(zhuǎn),也作用于我們的平移。這實際上比聽起來更簡單。
為了表示旋轉(zhuǎn),Ogre不是像一些圖形引擎那樣使用變換矩陣,而是使用四元組。四元組的數(shù)學意義是一個較難理解的四維線性代數(shù)。幸好,你不必去了解它的數(shù)字知識,了解如何使用就行了。非常簡單,用四元組旋轉(zhuǎn)一個向量只要將它們兩個相乘即可。現(xiàn)在,我們希望將所有作用于場景節(jié)點的旋轉(zhuǎn),也作用在平移向量上。通過調(diào)用SceneNode::getOrientation(),我們能得到這些旋轉(zhuǎn)的四元組表示,然后用乘法讓它作用在平移節(jié)點。
還有一個缺陷要引起注意的是,我們必須根據(jù)從上一幀到現(xiàn)在的時間,來對平移的大小進行縮放。否則,你的移動速度取決于應用程序的幀率。這確實不是我們想要的。這里有一個函數(shù)供我們來調(diào)用,可以避免平移攝像機時帶來的問題:
mCamNode->translate(transVector*evt.timeSinceLastFrame,Node::TS_LOCAL);
好了,我們來介紹一些新的東西。當你對一個節(jié)點進行平移,或者是繞某個軸進行旋轉(zhuǎn),你都能指定使用哪一個“變換空間”來移動它。一般你移動一個對象時,不需要指定這個參數(shù)。它默認是TS_PARENT,意思是這個對象使用的是父節(jié)點所在的變換空間。在這里,父節(jié)點是場景的根節(jié)點。當我們按下W鍵時(向前移動),我們走向Z軸負的方向。如果我們不在代碼前面指明TS_LOCAL,攝像機都會向全局負Z軸移動。然而,既然我們希望按下W鍵時攝像機往前走,我們就需要它往節(jié)點所面朝的方向走。所以,我們使用“本地”變換空間。
還有另一種方式我們也能實現(xiàn)(盡管不是怎么直接)。我們獲取節(jié)點的朝向,一個四元組,用方向向量乘以它,能得到相同的結(jié)果。這完全是合法的:
// Do not add this to the program
mCamNode->translate(mCamNode->getOrientation()*transVector*evt.timeSinceLastFrame, Node::TS_WORLD);
這同樣也在本地空間里進行移動。在這里,實際上沒這樣做的必要。Ogre定義了三種變換空間:TS_LOCAL, TS_PARENT, 和TS_WORLD。也許存在其它的場合,你需要使用另外的向量空間來進行平移或旋轉(zhuǎn)。若真是這樣,你可以像上面的代碼那樣做。找到一個表示向量空間的四元組(或者你要操作的物體的朝向),用平移向量去乘它,得到一個正確的平移向量,然后用它在TS_WORLD空間里移動。目前應該不會遇到這樣的情況,我們后面的教程也不會涉及這些內(nèi)容。
好了,我們有了鍵盤控制的移動。我們還想實現(xiàn)一個鼠標效果,來控制我們觀察的方向,但這只有在鼠標右鍵被按住的情況下。為了達到目的,我們首先要檢查右鍵是否被按下去:
if (mMouse->getMouseState().buttonDown(OIS::MB_Right))
{
如果是,我們根據(jù)從上一幀開始鼠標移動的距離來控制攝像機的俯仰偏斜。為了達到目的,我們?nèi)〉肵、Y的相對改變,傳到pitch和yaw函數(shù)里去:
mCamNode->yaw(Degree(-mRotate * mMouse->getMouseState().X.rel), Node::TS_WORLD);
mCamNode->pitch(Degree(-mRotate * mMouse->getMouseState().Y.rel), Node::TS_LOCAL);
}
注意,我們使用TS_WORLD向量坐標來進行偏斜(如果不指明的話,旋轉(zhuǎn)函數(shù)總是用TS_LOCAL作為默認值)。我們要保證物體隨意的俯仰不影響它的偏斜,我們希望總是繞著固定的軸進行偏斜轉(zhuǎn)動。如果我們把yaw設(shè)置成TS_LOCAL,我們就能得到這樣的:
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -