標籤:
這章的重點會在模型動作和它們通常是怎樣工作上,特別是在Ogre 3D 方面。沒有運動,那麼一個3D情境是沒有生機的。運動是使情境現實和有趣的最重要的因素之一。
在這一章,我們將會:
※ 播放一個運動。
※把兩個運動結合起來。
※在運動上關聯實體。
一。【 添加運動 】
在之前的章節中,我們使用使用者輸入添加了程式的互動性。現在我們準備添加運動來增加另一種形式的互動性。運動對於每個情境是非常重要的因素。沒有它們,每個情境看起來將會是靜止的而且死氣沉沉的,但是加入運動後,情境將會生動起來。所以讓我們添加它們吧。
一如往常,我們將會使用之前章節的代碼,但是這次我們什麼也不刪除。
1. 對於我們的運動,我們需要在幀監聽中添加新的成員變數。添加一個指標來儲存我們想要運動的實體,另一個指標儲存運動狀態:
Ogre::Entity* _ent;Ogre::AnimationState* _aniState;
2. 然後改變幀監聽的建構函式把實體的指標作為一個新的參數:
Example34FrameListener(Ogre::SceneNode* node,Ogre::Entity* ent,RenderWindow* win,Ogre::Camera* cam)
3. 在建構函式的函數體中,把參數的實體指標賦值給新的成員變數:】
_ent = ent;
4. 在這之後,從實體中調用Dance動作並儲存到專門為它而建立的成員變數中。最後,設定運動為enabled並迴圈運動。
_aniState = _ent->getAnimationState("Dance");_aniState->setEnabled(true);_aniState->setLoop(true);
5. 然後,我們需要告訴運動自從上一次更新到現在已經過去多長時間。我們將會在frameStarted()函數中添加這件事;通過這個我們就可以。
_aniState->addTime(evt.timeSinceLastFrame);
6. 我們最後需要做的就是調整ExampleApplication 類讓它可以與新的幀監聽一起作用。添加一個新的成員變數到程式來儲存一個實體指標。
Ogre::Entity* _SinbadEnt;
7. 把原來建立的局部指標分配給新建立的實體來儲存。把
Ogre::Entity* Sinbad = mSceneMgr->createEntity("Sinbad", "Sinbad.mesh");
替換為
_SinbadEnt = mSceneMgr->createEntity("Sinbad", "Sinbad.mesh");
8. 當關聯實體到結點也需要做同樣的改變。
_SinbadNode->attachObject(_SinbadEnt);
9. 當然,當建立新的幀監聽時,添加新的參數到調用的建構函式。
Ogre::FrameListener* FrameListener = new Example34FrameListener(_SinbadNode,_SinbadEnt,mWindow,mCamera);
10. 編譯運行程式。你將會看到Sinbad 在跳舞。
【 剛剛發生了什麼 ? 】
用短短几行代碼,我們實現了Sinbad的跳動。在第一步中,我們添加兩個成員變數,用於運動模型。第一個指標變數使用者儲存模型。第二個Ogre::AnimationState的指標變數,被Ogre 3D用於描述單一運動和其相關聯的資訊。第二和第三步就比較容易理解了,第二步我們改變了建構函式來適應新指標,第三步儲存了第一步中建立的成員變數。第四步就比較有趣了,我們請求實體返回給我們名為”Dance”的運動。每個實體變數儲存了實體本身所有的運動,並且我們可以使用字串變數和getAnimationState() 函數查詢運動。這個函數返回運動的AnimationState指標,如果運動為空白,它將會返回一個null 指標。在擷取完運動的狀態,我們啟用它。
這告訴Ogre 3D 播放這個動作。同樣,我們設定迴圈屬性為真,這樣這個動作就可以迴圈播放,直到我們停止它為止。第五步是重要的一步,使用這行代碼,使實體栩栩如生起來。每次情境被渲染時,然後添加運動時間,這樣Ogre 3D 就可以播放了。確切點說,它相應自上一幀到現在的過去時間。這可能要由Ogre 3D 自己完成,但是這種方式更靈活。比如,我們可以添加第二個模型,但是我們想要它做個慢動作。如果Ogre 3D 自己更新動作,那麼不論是以普通速度還是以慢速運動,模型的運動對我們來說將是很困難的。但是我們自己完成,我們可以把evt.timeSinceLastFrame *0.25便可以實現慢動作。在這步之後,便可以對建構函式進行小的修改,使之與幀監聽的建構函式相容。這樣,我們需要儲存想要運動的實體指標。
【 簡單測試 —— 時間的重要性 】
為什麼我們需要告訴Ogre 3D自從上次更新到現在時間過去了多少?這種方式的優點是什嗎?
二。【 同時控制兩個動作 】
添加第二個模型,這個模型站在第一個模型旁邊,但是以一個慢動作在運動。你應看到兩個模型在顯示運動的不同階段,就好像下面圖片顯示的那樣:
在添加我們第一個動作之後,我們將會研究為什麼和怎樣同時播放動作。
這裡,我們將會使用與建立第一個例子同樣的辦法添加第二個動作:
1. 從Dance,改變運動到RunBase。
_aniState = _ent->getAnimationState("RunBase");
2. 編譯運行程式。你可以看到Sinbad在跑動,但只是上半身的不動的原地跑動。
3. 對於我們第二個動作,我們需要一個儲存運動狀態的新指標:
Ogre::AnimationState* _aniStateTop;
4. 然後,我們需要擷取運動的狀態,啟用並迴圈運動。我們需要調用的運動名為Runtop。
_aniStateTop = _ent->getAnimationState("RunTop");_aniStateTop->setEnabled(true);_aniStateTop->setLoop(true);
5. 最後要做的一件事就是添加自上幀過去的時間,就好像我們第一次設定的那樣:
_aniStateTop->addTime(evt.timeSinceLastFrame);
6. 然後編譯運行程式。現在你可以看到Sinbad整個身子做跑的動作。
【 剛剛發生了什麼 ?】
我們在同時使用了兩個運動。在之前,如果你問自己為什麼不調用像playAnimation(AnimationName)這樣的函數,反而需要擷取AnimationState來播放運動。現在你有答案了。Ogre 3D 支援在同一時間使用多種運動,但使用playAnimation(AnimationName)卻實現不了。我們甚至可通過使用一個修改的變數和addTime()函數,以不同速度來控制模型。
三。【讓我們的模型走兩步 】
我們已經有個行走的運動了,但是模型並沒有改變它的位置。我們將會添加基礎的模型運動控制並使用我們所學來混合運動。
一如既往,我們將會使用之前的代碼來作為起點:
1. 首先,我們需要幀監聽中兩個變數來控制移動和儲存旋轉。
float _WalkingSpeed;float _rotation;
2. 在建構函式中,我們初始化新的變數;我們每秒移動50個單位並以無旋轉作為開始:
_WalkingSpeed =50.0f;_rotation =0.0f;
3. 然後我們需要改變運動狀態並阻止它迴圈。這次,當新的運動開始時,我們需要控制模型而非交給Ogre 3D 控制。
_aniState = _ent->getAnimationState("RunBase");_aniState->setLoop(false);_aniStateTop = _ent->getAnimationState("RunTop");_aniStateTop->setLoop(false);
4. 在frameStarted() 方法中,我們需要兩個局部變數,一個是為了顯示這幀我們是否移動了模型,第二個變數是用於儲存模型移動的方向。
bool walked = false;Ogre::Vector3 SinbadTranslate(0,0,0);
5. 同樣在方法中,我們添加新的代碼來控制模型的移動。我們將會使用方向鍵來移動。當一個鍵按下的時候,我們需要儲存變換變數來儲存模型移動的方向,並且需要以它移動的方向來設定旋轉變數以旋轉模型
if(_key->isKeyDown(OIS::KC_UP)){ SinbadTranslate += Ogre::Vector3(0,0,-1); _rotation =3.14f; walked = true;}
6. 我們需要以同樣的方式添加另外三個方向鍵:
if(_key->isKeyDown(OIS::KC_DOWN)){ SinbadTranslate += Ogre::Vector3(0,0,1); _rotation =0.0f; walked = true;}if(_key->isKeyDown(OIS::KC_LEFT)){ SinbadTranslate += Ogre::Vector3(-1,0,0); _rotation =-1.57f; walked = true;}if(_key->isKeyDown(OIS::KC_RIGHT)){ SinbadTranslate += Ogre::Vector3(1,0,0); _rotation =1.57f; walked = true;}
7. 然後,在按鍵處理之後,我們需要檢查一下這幀中 walked 是否為 true。如果是這種情況,我們需要檢查運動是否停止。當為true時,我們重新開始運動:
if(walked){ _aniState->setEnabled(true); _aniStateTop->setEnabled(true); if(_aniState->hasEnded()) { _aniState->setTimePosition(0.0f); } if(_aniStateTop->hasEnded()) { _aniStateTop->setTimePosition(0.0f); }}
8. 如果我們這幀不動的話,需要設定兩個運動的狀態到0。否則,我們的模型將會看起來像在進行動作的一半時被凍住一般,而這看起來的效果不好。所以如果我們這幀不需要行走,我們需設定兩個運動回到其起點位置。同樣,當這幀不移動模型的時候,我們將會凍結兩個運動,因為此時我們並不需要運動。
else{ _aniState->setTimePosition(0.0f); _aniState->setEnabled(false); _aniStateTop->setTimePosition(0.0f); _aniStateTop->setEnabled(false);}
9. 最後我們需要做的就是應用變換和旋轉到模型的情境結點:
_node->translate(SinbadTranslate * evt.timeSinceLastFrame * _ WalkingSpeed);_node->resetOrientation();_node->yaw(Ogre::Radian(_rotation));
10 現在我們編譯運行程式。在滑鼠和WASD鍵的作用下,我們可以移動攝像機。用方向鍵,我們就可以移動Sinbad。每次移動他的時候,他便會做出正確的運動。
【剛剛發生了什麼】
我們綜合了使用者輸入和運動建立了第一個程式。這可以稱得上我們目前為止第一個真正的互動程式。在第一步和第二步,我們建立和初始化了我們需要的變數。在第三步,我們改變了過去運動的方式;準確的說,我們之前總是直接啟用運動並迴圈它。現在我們不想要直接啟用它,因為我們只需要在移動的時候使用運動,除此之外的情況,看起來都很stupid。這也是我們凍結運動的迴圈的原因。我們只想要反應使用者的輸入,這樣就沒必要一直迴圈了。如果需要,我們將會自己啟動運動。
我們基本上是在frameStarted() 方法中作出的改變。在第四步中,我們建立了一對稍後使用的局部變數。一個是來表示這幀模型是否移動bool類型的開關,另一個是表示移動方向的向量。第五步和第六步中查詢方向鍵的狀態。當一個鍵按下,我們改變方向向量,做相應的旋轉並設定移動的開關為true。在第七步中我們使用這個開關,如果flag為true,意味著這幀模型會移動,我們啟用運動並檢查是否有運動達到它們的結尾。如果運動到達結尾,我們重設它們到起點這樣,它們就可以再次繼續播放了。因為當模型不移動的時候,我們不想要播放運動,所以在第八步我們設定它們運動為起點並凍結它們。在第九步中,我們應用了變換。然後重設旋轉,在這之後,應用新的旋轉。這一步很是必要,因為yaw函數添加旋轉到現有的旋轉,我們需要絕對的旋轉而不是相對的旋轉。因此,我們先重設旋轉然後應用旋轉到現在的歸零旋轉上。
四。【 添加雙刀】
我們現在有一個可以通過使用者輸入控制行走和運動的模型。現在我們準備研究我們如何添加一個對象到運動模型上。
1. 在createScene函數之後,建立兩把刀模型的執行個體並命名為Sword1 和 Sword2:
Ogre::Entity* sword1 = mSceneMgr->createEntity("Sword1", "Sword.mesh");Ogre::Entity* sword2 = mSceneMgr->createEntity("Sword2", "Sword.mesh");
2. 現在使用一個名稱來關聯刀到模型:
_SinbadEnt->attachObjectToBone("Handle.L", sword1);_SinbadEnt->attachObjectToBone("Handle.R", sword2);
3.編譯運行程式。你將會看到Sinbad手裡有兩把刀。
【 剛剛發生了什嗎?】
我們建立了兩把刀的執行個體並關聯它們到骨骼上。建立的執行個體並非難以理解。剛才的代碼中比較難但比較有趣的部分就是調用了函數attachObjectToBone()。為理解這個函數的工作原理,我們需要討論一下運動是如何儲存和播放的。
×運動
對於運動來說,我們使用一種稱為skeletons(骨架) 和 bones(骨骼) 的東西。這個系統的靈感是來自於自然;在自然界,基本上所有的生物都有一個骨架來支撐它。在骨架和一些肌肉的協助之下,動物和人類可以朝一個確定的方向移動他們身體的一部分。比如,我們可以使用手指的關節來把手指握成一個拳頭。在電腦圖形學中運動也是以同樣的方式來運行。美工定義3D模型而且為其建立骨架,這樣模型就可以運動了。骨架是由骨骼和關節構成。關節是串連兩塊骨骼並且定義骨骼的運動方向。在這裡是簡化的骨架圖;而通常,要比這裡的多很多。
用關節,骨骼和骨骼的作用半徑,一個美工就可以建立複雜的運動,比如我們正在使用的Sinbad運動。就好像運動,骨骼也是有名字的。Ogre 3D 讓我們使用這些骨骼作為關聯別的實體的點。這有著巨大的優勢,當關聯實體後,實體也會像骨骼一樣得到旋轉,這意味著如果我們在Sinbad的手中有一個關聯點並關聯刀之後,雙刀就會一直在手中。因為當雙手做動作時,刀也會得到同樣的運動。如果這個函數不存在,那幾乎不可能添加模型到手上或者關聯東西到他們的後面,就如我們對刀做的操作一樣。
五。【 列印輸出模型的所有動作 】
我們已經知道美工定義運動的名字,但很多時候直接從Ogre 3D來擷取運動的名字很重要。當我們沒有製作這個模型的美工來詢問裡面有什麼運動或當你想要檢查一下匯出的處理是否成功,擷取名字這件事就顯得十分重要了。現在我們將會研究如何在控制台中列印模型的所有運動。
我們將會使用之前的代碼作為起點來列印我們實體所有的運動。
1. 在createScene() 函數最後,用set擷取模型所有的運動:
Ogre::AnimationStateSet* set = _SinbadEnt- >getAllAnimationStates();
2. 然後定義一個迭代器並用一個set迭代器來初始化:
Ogre::AnimationStateIterator iter = set- >getAnimationStateIterator();
3. 並且,最後,遍曆所有的動作並列印它們的名字:
while(iter.hasMoreElements()){ std::cout << iter.getNext()->getAnimationName() << std::endl;}
4. 編譯運行程式。在開始程式並載入情境之後,你將會在控制台程式中看到下面的文字。
Dance
DrawSwords
HandsClosed
HandsRelaxed
IdleBase
IdleTop
JumpEnd
JumpLoop
JumpStart
RunBase
RunTop
SliceHorizontal
SliceVertical
【 剛剛發生了什嗎?】
我們請求實體返回給我們包含運動資訊的資料集。我們然後遍曆這個資料集並列印出運動的名稱。我們看到有好多沒有用過的和已經用過的運動。
【本章回顧】
在這章我們學習了很多有關運動和如何使用它來使我們的3D更有趣的方法。
具體的說,我們所學涵蓋了以下內容:
※如何擷取實體的運動並使用它。
※如何啟用,凍結和迴圈程式和為什麼需要告訴運動自上幀過去有多長時間。
※如何在同一時間使用兩個運動。
※運動是如何使用骨架的?如何關聯一個實體到一骨骼上?
※如何查詢實體包含的所有運動。
在下一章,我們將會研究Ogre 3D的另一個新方面,主要是關於使用不同的情境管理器和為什麼要這樣做。
【轉】使用Ogre 3D 運動模型 -----OGRE 3D 1.7 Beginner‘s Guide中文版 第五章