目錄
Oogst’s How To. 1
如何用ExampleApplication建立一個應用程序... 3
如何在場景(scene)中放置一個3D模型... 4
如何從場景中移除一個實體... 4
如何移動、重置、縮放及旋轉一個場景節點... 5
如何在場景那個中放置光源... 5
如何設置環境光... 5
如何控制相機... 6
用鼠標控制相機... 6
如何向場景中添加布告板... 7
如何用ExampleFrameListener創建基本的FrameListener
7
如何在FrameListener中從scene中控制對象... 8
如何得到自前一幀以後的時間... 8
如何響應按鍵... 9
如何確保按鍵在彼此太短時間內不響應... 9
如何退出應用程序... 10
如何在運行過程中從場景中高效地添加和移除3D對象(比如發射火箭)... 10
如何顯示一個Overlay(及隱藏)... 12
如何改變TextArea中的文本... 12
如何顯示鼠標指針(mouse-cursor)... 13
如何創建一個工作按鈕(working button)... 13
如何找出哪個按鈕被按下... 14
如何使用ActionListener退出應用程序... 15
如何獲得一個不同的SceneManager
16
如何有效地獲得所有可能碰撞的列表... 16
如何找出一個MoveableObject屬於你的哪個對象... 17
如何從碰撞檢測中排除對象... 18
如何用ExampleApplication建立一個應用程序
如果你還不知道這個是如何工作的,你必須在Ogre指南章節中讀更多關於這個的知識:完全理解它確實很重要。簡言之,它需要創建一個繼承自ExampleApplication的類(class)。這個類能夠執行函數createScene()及createFrameListener()。它大致上看上去如下:
#include "ExampleApplication.h"
class MyClass: public ExampleApplication,
{
public:
MyClass(void);
~MyClass(void);
protected:
void createScene(void);
void createFrameListener(void);
};
你還會需要一個創建MyClass實例的.cpp文件並運行它。通常是這個樣子(取自於“Guide To Setting Up Application Project Files”-tutorial):
#include "Ogre.h"
#include "MyClass.h"
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char **argv)
#endif
{
MyClass app;
try
{
app.go();
}
catch( Exception& e )
{
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!",
MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occured: %s/n", e.getFullDescription().c_str());
#endif
}
return 0;
}
如何在場景(scene)中放置一個3D模型
一個3D模型是一個實體(Entity)並且它必須附屬於一個場景結點(SceneNode)以被放置在場景中。場景結點能夠由SceneManager中的rootScnenNode產生。創建一個實體和場景結點並把實體掛在場景結點上是這個樣子:
Entity* thisEntity = mSceneMgr->createEntity("nameInTheScene", "FileName.mesh");
SceneNode* thisSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
thisSceneNode->attachObject(thisEntity);
如何從場景中移除一個實體
爲了移除一個實體(Entity),你必須把實體從它的場景結點中分開,刪除它如果需要的話還要銷燬場景結點,這必須用SceneManager來完成。如果你有一個叫做myNode的SceneNode*,你可以用下面的代碼完全地刪除它的內容(MovableObjects和子場景結點)及結點本身:
SceneNode* parent = entity->getParentSceneNode();
parent->detachObject(entity);
mSceneMgr->destroyEntity(entity->getName());
// entity is now destroyed, don't try to use the pointer anymore!
// optionally destroy node
mSceneMgr->destroySceneNode(parent->getName());
如何移動、重置、縮放及旋轉一個場景節點
如果你有一個連接有多個可移動對象(MovableObject),像實體、燈光及相機的場景結點,你可以用多個函數移動它,查看這些的API。下面的函數分別是移動(move)、重置(reposition)、縮放(scale)及繞它的X-,Y-,Z-軸進行旋轉:
thisSceneNode->translate(10, 20, 30);
thisSceneNode->setPosition(1.8, 20.1, 10.5);
thisSceneNode->scale(0.5, 0.8, 1.3);
thisSceneNode->pitch(45);
thisSceneNode->yaw(90);
thisSceneNode->roll(180);
如何在場景那個中放置光源
爲了向場景中添加一個光源,你必須向SceneManager請求一個。然後你可以對它進行設置,下面給出一些設置的例子:
Light* myLight = mSceneMgr->createLight("nameOfTheLight");
myLight->setType(Light::LT_POINT);
myLight->setPosition(200, 300, 400);
myLight->setDiffuseColour(1, 1, 0.7);
myLight->setSpecularColour(1, 1, 0.7);
你還可以把一個光源連接到一個場景結點上。下面的代碼創建一個場景結點並把myLight連接到上面:
SceneNode* thisSceneNode = static_cast<SceneNode*>(mSceneMgr->getRootSceneNode()->createChild());
thisSceneNode->attachObject(myLight);
如何設置環境光
環境光由SceneManager進行控制,因此你可以用SceneManager來對它進行設置:
mSceneMgr->setAmbientLight(ColourValue(0.2, 0.2, 0.2));
如何控制相機
ExampleApplication中的標準相機叫做mCamera並且它在繼承自ExampleApplication的類中是可用的。下面的代碼改變它的位置,改變它的朝向點,創建一個SceneNode並把相機連接到上面:
mCamera->setPosition(0, 130, -400);
mCamera->lookAt(0, 40, 0);
SceneNode* camNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
camNode->attachObject(mCamera);
用鼠標控制相機
這個代碼使得爲RTS(譯者注:Realtime Strategy Game,即時戰略類遊戲,對應回合制戰略遊戲)風格的遊戲使用標準相機移動接口變的容易。當按下左鍵時相機圍繞着一個固定的點移動。拉伸(zooming)通過滾輪或點擊中鍵及上下移動鼠標來完成。
Real MoveFactor = 60.0 * evt.timeSinceLastFrame;
// Move the camera around with the left button
if (mInputDevice->getMouseButton(1)) {
SceneNode *camNode = mCamera->getParentSceneNode();
if (camNode == 0) {
std::cerr << "mCamera isn't attached to any SceneNode !" << std::endl;
}
camNode->yaw(Degree(mInputDevice->getMouseRelativeX() * MoveFactor * -0.1));
camNode->pitch(Degree(mInputDevice->getMouseRelativeY() * MoveFactor * -0.1));
}
// Zoom with the middle button...
if (mInputDevice->getMouseButton(2)) {
mCamera->moveRelative(Vector3(0.0, 0.0, -0.5)
* mInputDevice->getMouseRelativeY() * MoveFactor);
}
// ... and the wheel ;-)
mCamera->moveRelative(Vector3(0.0, 0.0, -0.1)
* mInputDevice->getMouseRelativeZ() * MoveFactor);
如何向場景中添加布告板
佈告板(Billboard)是一個總是面向相機的正方形。它被認爲是一個精靈(sprite)。爲產生一個佈告板,你必須首先產生一個BillboardSet。然後billboard能被添加到上面一個給定的位置。BillboardSet是一個MovableObject,因此應該添加到SceneNode上。整個程序如下:
SceneNode* myNode = static_cast<SceneNode*>(mSceneMgr->getRootSceneNode()->createChild());
BillboardSet* mySet = mSceneMgr->createBillboardSet("mySet");
Billboard* myBillboard = mySet->createBillboard(Vector3(100, 0, 200));
myNode->attachObject(mySet);
如何用ExampleFrameListener創建基本的FrameListener
幀兼聽器(FrameListener)給你在每幀開始和結束時做一些事情的機會。首先你必須創建一個繼承自ExampleFrameListener的類並且在這個類中你可以執行frameStarted()和frameEnded()。看起來就像這樣子:
#include "ExampleFrameListener.h"
class myFrameListener: public ExampleFrameListener
{
public:
myFrameListener(RenderWindow* win, Camera* cam);
~myFrameListener(void);
bool frameStarted(const FrameEvent& evt);
bool frameEnded(const FrameEvent& evt);
};
構造函數應該調用它的父構造函數,像這樣子:
myFrameListener::myFrameListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam){}
你還必須向Root註冊你的FrameListener,這可以在createFrameListener()中完成——繼承自ExampleApplication的類的函數。你可以向root註冊盡你希望多的FrameListener。這個過程如下:
void createFrameListener(void)
{
MyFrameListener listener = new MyFrameListener(mWindow, mCamera);
mRoot->addFrameListener(listener);
}
如何在FrameListener中從scene中控制對象
如果你想在FrameListener中控制場景中的一些對象,FrameListener有權訪問它。一個簡單方法是在構造FrameListener時提供一個指向它的指針。現在這個構造函數該是這樣子:
myFrameListener(RenderWindow* win, Camera* cam, Car* car);
如何得到自前一幀以後的時間
在FrameListener的frameEnded()和frameStarted()函數中你可以用下面的方法得到以秒爲單位的時間(這是一個浮點數):
bool frameStarted(const FrameEvent& evt)
{
float time = evt.timeSinceLastFrame;
return true;
}
現在你可以馬上用每秒的速度乘以這個浮點數來得到自上一幀後的移動。這可以爲遊戲設定獨立的幀率。
如何響應按鍵
你可以在FrameListener的函數frameEnded()和frameStarted()中通過先讓Ogre捕獲然後再檢查哪一個鍵被按下來響應按鍵。你只需要每幀捕獲一次InputDevice。過程是這樣子:
mInputDevice->capture();
if (mInputDevice->isKeyDown(Ogre::KC_DOWN))
{
//react however you like
}
if (mInputDevice->isKeyDown(Ogre::KC_UP))
{
//react however you like
}
如何確保按鍵在彼此太短時間內不響應
如果你用上面的方法響應按鍵,它們每幀都會響應。比如說你想每幀大約響應兩次,你可以通過設置計時器(timer)來完成。爲了能夠使不同的函數調用來訪問它,timer必須在它自己的類中而不是在函數中
。執行這個是這樣子:
class myFrameListener: public ExampleFrameListener
{
protected:
float buttonTimer;
public:
myFrameListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam)
{
buttonTimer = 0;
}
bool frameStarted(const FrameEvent& evt)
{
float time = evt.timeSinceLastFrame;
buttonTimer -= time;
mInputDevice->capture();
if (mInputDevice->isKeyDown(Ogre::KC_DOWN) && buttonTimer <= 0)
{
buttonTimer = 0.5;
//react however you like
}
return true;
}
};
如何退出應用程序
你可以通過在FrameListener中frameStarted()或frameEnded()中返回false來退出你的程序。如果你想把這個關聯在鍵盤上的escape按鈕上,用下面的方法:
bool frameStarted(const FrameEvent& evt)
{
mInputDevice->capture();
if (mInputDevice->isKeyDown(Ogre::KC_ESCAPE))
return false;
return true;
}
如何在運行過程中從場景中高效地添加和移除3D對象(比如發射火箭)
在createScene()函數中你可以從一個文件中加載meshes,但是如果要在運行時添加一個新的對象這樣做就太慢了(雖然在一個很簡單的程序中看不出速度上的差別)。爲了添加對象更快,你可以在createScene()函數中加載許多meshes然後在運行時從棧(stack)中把它們取出來。在對象不再被使用後,你可以把它們放回棧中爲以後使用。這個的應用的一個好的例子是發射火箭:它們必須在開火的時候創建並在爆炸後被移除。
爲了能夠總是訪問到這個棧,你可以使它是一個全局變量(global variable)或通過一個singleton訪問它。後者更漂亮,但是要用更多的代碼因此在這個例子中我不這樣做。所以你在某處定義一個儲存Entities的棧:
stack rocketEntities;
在createScene()中用許多rockets填充這個棧。每一個Entity此時被設置爲不可見並且必須有一個唯一的名字,這個可以用sprintf()函數來完成。這一切如下所示:
for (unsigned int t = 0; t < 100; t++)
{
char tmp[20];
sprintf(tmp, "rocket_%d", t);
Entity* newRocketEntity = mSceneMgr->createEntity(tmp, "Rocket.mesh");
newRocketEntity->setVisible(false);
rocketEntities.push(newRocketEntity);
}
此刻當創建一個新的Rocket時我們可以從rocketEntities中取出一個mesh。在這個例子中我在Rocket構造函數中做這些,並在析構函數中把它壓回棧中。在析構函數中爲了能夠從場景中取回Entity,我把它的名字存儲在rocketEntityName中。而且我把rocket恰當地放在構造函數中。爲了使這一切工作,SceneManager在整個程序中必須是可用的,爲此通過讓它是一個全局變量(在所有class的外面)來完成。我還在在構造函數和析構函數中執行創建和銷燬SceneNode。至此這個Rocket-class看起來如此:
class Rocket
{
protected:
SceneNode* rocketNode;
string rocketEntityName;
public:
Rocket(const Vector3& position, const Quaternion& direction)
{
rocketNode = static_cast<SceneNode*>(sceneMgr->getRootSceneNode()->createChild());
Entity* rocketEntity = rocketEntities.top();
rocketEntities.pop();
rocketEntity->setVisible(true);
rocketEntityName = rocketEntity->getName();
rocketNode->attachObject(rocketEntity);
rocketNode->setOrientation(direction);
rocketNode->setPosition(position);
}
~Rocket()
{
Entity* rocketEntity = static_cast<Entity*>(rocketNode->detachObject(rocketEntityName));
rocketEntity->setVisible(false);
rocketEntities.push(rocketEntity);
sceneMgr->getRootSceneNode()->removeAndDestroyChild(rocketNode->getName());
}
};
如果你使用這個結構,你應該意識到這個事情:如果沒有rockets剩餘會發生錯誤。你可以通過檢查這個Entities棧是否爲空,如果爲空就加載新的meshes到棧中來解決這個問題。
如何顯示一個Overlay(及隱藏)
爲了顯示和隱藏一個Overlay,你首先必須用OverlayManager(這是一個singleton)得到一個指向它的指針。如果你定義了一個名字是“myOverlay”的.overlay腳本,你可以用下面的代碼獲取、顯示並隱藏它:
Overlay* thisOverlay = static_cast<Overlay*>(
OverlayManager::getSingleton().getByName("myOverlay"));
thisOverlay->show();
thisOverlay->hide();
如何改變TextArea中的文本
你可以在運行時間改變GUI中Containers和Elements中所有的參數(parameter)。爲此,首先你必須得到一個你想要的Element或Container的指針,如果是基於你想做的東西的需要就把它設計成那種類型。得到一個指向在.overlay腳本中定義爲“myTextArea”的TextArea的指針並改變其caption類似這樣:
OverlayElement* thisTextArea = OverlayManager::getSingleton().getOverlayElement("myTextArea");
thisTextArea->setCaption("blaat");
這個例子中不需要casting,因爲每個OverlayElement都有caption。如果你想設置一個OverlayElement類型的特殊設置,你必須把它設計爲那種類型。改變一個textArea的font-name類似這樣:
TextAreaGuiElement* thisTextArea = static_cast<TextAreaOverlayElement*>(
OverlayManager::getSingleton().getOverlayElement("myTextArea"));
thisTextArea->setFontName("RealCoolFont");
如何顯示鼠標指針(mouse-cursor)
這一條已經過時了。新的CEGUI方法的詳述在這裏:
How To Show The Mouse Cursor, 以及這裏: Intermediate Tutorial 3
如果你想在屏幕上顯示鼠標指針,你須做兩件事:設置它爲顯示並告訴FrameListener追蹤它。令它顯示可以如下做:
GuiContainer* cursor = OverlayManager::getSingleton().getCursorGui();
cursor->setMaterialName("Cursor/default");
cursor->setDimensions(32.0/640.0, 32.0/480.0);
cursor->show();
讓FrameListener追蹤它應在它的父構造函數中通過設置它的最後的boolean參數爲true來做。至此這個構造函數看起來是這個樣子:
myFrameListener::myFrameListener(RenderWindow* win, Camera* cam)
: ExampleFrameListener(win, cam, false, true){}
注意這樣做後,因爲mInputDevice的capture()函數和isKeyPressed()函數不再正確地工作所以這個特殊的FrameListener對按下按鈕操作不再反應。現在需要一個不同的FrameListener來執行鍵盤輸入。
如何創建一個工作按鈕(working button)
這一條已經過時並由cegui方案替代。
你可以在Overlay腳本中用類似下面的句法定義一個button,這對設置所有的這些material很重要:
container Button(myButton)
{
metrics_mode relative
horz_align left
vert_align top
top 0.1
left 0.1
width 0.18
height 0.1
material NCG/GuiSession/RedMaterial
button_down_material NCG/GuiSession/RedMaterial
button_up_material NCG/GuiSession/RedMaterial
button_hilite_down_material NCG/GuiSession/RedMaterial
button_hilite_up_material NCG/GuiSession/RedMaterial
button_disabled_material NCG/GuiSession/RedMaterial
}
Buttons在Ogre中不是標準的,因此你須確保它們能在文件夾ogrenew/PlugIns/GuiElements/Include中被編譯器(compiler)找到。現在你可以包含它:
#include "OgreButtonGuiElement.h"
爲使這個button工作,一個ActionListener必須被註冊到它上面,這可以如下完成:
ActionTarget* thisButton = static_cast<ButtonGuiElement*>(
GuiManager::getSingleton().getGuiElement("myButton"));
thisButton->addActionListener(this);
此例中假設‘this’是一個ActionListener(默認不是這樣子的)。當然任何ActionListener都可以,不必是‘this’。你可以通過繼承ActionListener來使一個類是一個ActionListener並執行actionPerformed()。這個過程類似這樣:
class Listener: public ActionListener
{
public:
void actionPerformed(ActionEvent *e);
};
如何找出哪個按鈕被按下
這一條已經過時並由cegui方案來替代。
如果你註冊一個ActionListener給幾個button,它們會調用同一個函數actionPerformed()。你可以通過比較它的名字與ActionEvent e的名字來找出是哪個button被按下。如下:
#include
void actionPerformed(ActionEvent *e)
{
std::string action = e->getActionCommand();
if (action == "myButton")
{
//handle the button-press
}
}
如何使用ActionListener退出應用程序
這個條目已經過時,能用cegui方案代替。
退出應用程序可以在FrameListener中的frameStarted()函數和frameEnded()函數完成,而不是在actionPerformed()函數中。那麼我們要怎麼做呢?爲此我們可以介紹一個簡單的FrameListener,如果要求退出它只作退出而不做其他任務。這個FrameListener看起來如下(來自於Ogre中提供的Gui-demo):
#include "ExampleFrameListener.h"
class QuitListener: public ExampleFrameListener
{
public:
QuitListener(RenderWindow* win, Camera* cam): ExampleFrameListener(win, cam, false, false)
{
quit = false;
}
bool frameStarted(const FrameEvent& evt)
{
if (quit)
return false;
return true;
}
void scheduleQuit(void)
{
quit = true;
}
protected:
bool quit;
};
至此如果你有一個指向QuitListener的指針,你可以通過調用scheduleQuit()來確定退出時間並且QuitListener會在下一幀開始前執行它。
如何獲得一個不同的SceneManager
場景是通過一個指示是哪種場景類型的標誌符來自動選擇的。這個過程是在ExampleApplication中完成的,因此要改變它,你必須改變ExampleApplication。通常,ExampleApplication包含下面的代碼:
virtual void chooseSceneManager(void)
{
// Get the SceneManager, in this case a generic one
mSceneMgr = mRoot->getSceneManager(ST_GENERIC);
}
你可以更改ST_GENERIC爲下面的任意標誌符來獲得一個適應你的特殊場景的場景管理器(SceneManager):
ST_GENERIC
ST_EXTERIOR_CLOSE
ST_EXTERIOR_FAR
ST_EXTERIOR_REAL_FAR
ST_INTERIOR
如何有效地獲得所有可能碰撞的列表
Ogre可以爲你提供一個可能碰撞的所有對象的列表。不在碰撞中的封閉的對象也在這個列表中,所以然後你須自己確定哪一個是碰撞對象。你可以用IntersectionSceneQuery來尋找這個碰撞列表,可以用下面的代碼來得到:
IntersectionSceneQuery* intersectionQuery = sceneMgr->createIntersectionQuery();
現在你可以找到一個所有可能碰撞的列表:
IntersectionSceneQueryResult& queryResult = intersectionQuery->execute();
如果你想在更形場景前得到幾次這個列表,你沒必要讓Ogre一次次地計算。你可以用下面的代碼不用新的計算而再次得到相同的列表:
IntersectionSceneQueryResult& queryResult = intersectionQuery->getLastResults();
使用後,你可以存儲IntersectionSceneQuery爲以後使用或移除它。如果移除它,你須用下面的代碼告訴SceneManager來完成:
mSceneMgr->destroyQuery(intersectionQuery);
IntersectionSceneQueryResult是一個可移動對象對(pairs of MovableObjects)的列表。它的應用的一個例子是獲得發生第一次碰撞的兩個對象的名字:
queryResult.movables2movables.begin()->first->getName();
有關結果類型的詳細資料參看Ogre-API。
如何找出一個MoveableObject屬於你的哪個對象
IntersectionSceneQuery傳遞過來的碰撞對象列表是可移動對象(MovableObject)的,但是一般來說你可能想把這些關聯到你自己的場景中的自定義對象。爲此,你可以把指針連接到你自己的object中指向一個MovableObject,稍後取回指針。如果你有一個Entity(是一個MovableObject)和一個自定義object(就像下面的myRocket),你可以把你的自定義object掛到Entity上,如下:
Entity* myEntity = sceneMgr->createEntity("myEntity", "Rocket.mesh");
Rocket* myRocket = new Rocket();
myEntity->setUserObject(myRocket);
現在你可以用下面的代碼從Entity中得到一個指向myRocket的指針:
myEntity->getUserobject();
爲了使你自定義的類工作,它必須繼承自UseDefinedObject。這還允許你找出返回的object是哪種class,這樣你可以cast it back。現在Rocket的定義是這樣子:
class Rocket: public UserDefinedObject
{
public:
const string& getTypeName(void) const
{
static const string typeName = "Rocket";
return typeName;
}
};
在檢測出哪個MovableObjects與碰撞相關後,你也可以找出你自己的哪些objects與碰撞相關。
如何從碰撞檢測中排除對象
你可以使用標記(flag)來從碰撞檢測(collision detection)中排除某些物體。你可以給任何可移動物體添加標記,比如下面例子中的100:
Entity* ground = mSceneMgr->createEntity("ground", "Ground.mesh");
ground->setQueryFlags(100);
現在你可以告訴你的IntersectionSceneQuery來排除物體用這個標記從它的intersections列表中通過設置它的mask。如下所示:
IntersectionSceneQuery* intersectionQuery = sceneMgr->createIntersectionQuery();
intersectionQuery->setQueryMask(~100);