Irrlicht 遊戲引擎開發 cooliris 界面(三)

(三)着重於介紹如何讓場景動起來,以及如何獲取和處理消息。

源碼:example_3.zip

 

1. Irrlicht 的運動機制

 

所謂運動,實際上計算機在不停得繪製場景,每繪製一次稱之爲一幀。 當各幀中物體的位置或外觀有所變化,那麼它就動起來了。 在irrlicht中,繪製一幀是在run循環中完成的:

 

Cpp代碼 複製代碼 收藏代碼
  1. while (device->run())   
  2. {   
  3.     if (device->isWindowActive())   
  4.     {   
  5.         driver->beginScene(truetrue, video::SColor(0, 0, 0, 0));   
  6.         scene_mgr->drawAll();  // 繪製一幀   
  7.         driver->endScene();   
  8.     }   
  9. }   
  10. device->drop();  
    while (device->run())
    {
        if (device->isWindowActive())
        {
            driver->beginScene(true, true, video::SColor(0, 0, 0, 0));
            scene_mgr->drawAll();  // 繪製一幀
            driver->endScene();
        }
    }
    device->drop();

 

我們所要做的,就是在 drawAll() 函數中,更新物體的位置,那麼場景就動起來了。

 

所有的一切,都與 scene::ISecenNodeAnimator 這個接口相關。凡是實現這個接口的類實例,都可以通過 addAnimator() 函數加入到 ISceneNode 的 animators 列表中。

 

SceneManager 的drawAll() 函數在繪製(render)場景前,會調用 OnAnimate() 函數。這個函數是遞歸的,以保證加入場景中的每個 SceneNode 都被調用。 在OnAnimate() 函數中, SceneNode 的每一個 ISecenNodeAnimator 的 animateNode() 函數都會被調用,以更新 SceneNode 的位置、大小或紋理。

 

整理其調用順序如下:

 

1. SceneManager           --> drawAll()                                   繪製一幀

2. ISceneNode               --> OnAnimate()                             SceneNode運動  

3. ISceneNodeAnimator --> animateNode(ISceneNode)       實現運動的具體函數

4. SceneManager           --> render()                                    繪製

 

可見,只要實現 ISecenNodeAnimator 接口,並加入到 SceneNode 中,就能夠讓其動起來。

 

2. Irrlicht 的消息傳遞

 

Irrlicht消息的傳遞是從device->run()開始的,在windows中首先調用 WndProc() 收集消息,打包成其內部的SEvent結構,再由 postEventFromUser() 將消息依次傳遞給 UserReceiver, GUI 和 3D Scene。

 

Irrlicht 中所有處理消息的類都必須實現 IEventReciever 接口。UserReceiver 就是在CreateDevice函數中指定的一個 IEventReceiver。 也就是說,消息處理的優先級爲 UserReceiver > GUI > 3D Scene。

 

在我們的程序中,並沒有指定UserReceiver,暫時也沒有GUI,所以消息直接交給了 SceneManager。

SceneManager 的消息,也由 ISceneManager 的 postEventFromUser() 傳遞。 這個函數的實現如下:

 

Cpp代碼 複製代碼 收藏代碼
  1. bool CSceneManager::postEventFromUser(const SEvent& event)   
  2. {   
  3.     bool ret = false;   
  4.     ICameraSceneNode* cam = getActiveCamera();   
  5.     if (cam)   
  6.         ret = cam->OnEvent(event);   
  7.   
  8.     _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;   
  9.     return ret;   
  10. }  
bool CSceneManager::postEventFromUser(const SEvent& event)
{
	bool ret = false;
	ICameraSceneNode* cam = getActiveCamera();
	if (cam)
		ret = cam->OnEvent(event);

	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
	return ret;
}

 

也就是說,只有當前Active(有效)的 ICameraSceneNode 纔會接收到消息。ICameraSceneNode 會檢測ISceneNodeAnimator 的isEventReceiverEnabled(), 如果爲真則調用其 OnEvent 函數。

 

整理消息傳遞的機制如下:

 

1. IrrlichtDevice              --> run()                                       蒐集消息並打包

2. IrrlichtDevice              --> postEventFromUser()             傳遞消息到userReceiver GUI 和 Scene

3. ISceneManager          --> postEventFromUser()             傳遞消息到CameraNode

4. ICameraSceneNode   --> onEvent()                                調用 Animator 的onEvent

5. ISceneNodeAnimator --> onEvent()  // if enabled           響應消息。

 

可見,實現 ISceneNodeAnimator 雖然可以 SceneNode 動起來,但只有在 CameraSceneNode 上,才能夠接收和處理消息。

 

3. 實現CameraAnimator

 

如果我們實現一個 CameraAnimator,在其中處理消息,更新 CameraNode 和 CubeNode 的位置,就可以實現交互和動畫。該類關鍵的幾個函數如下:

 

Cpp代碼 複製代碼 收藏代碼
  1. class CameraAnimator : public ISceneNodeAnimator   
  2. {   
  3. public:   
  4.     //! Constructor   
  5.     CameraAnimator(...   
  6.   
  7.     //! Destructor   
  8.     virtual ~CameraAnimator(){};   
  9.   
  10.     //! 更新 SceneNode 位置   
  11.     virtual void animateNode(ISceneNode* node, u32 timeMs);   
  12.   
  13.     //! 處理消息   
  14.     virtual bool OnEvent(const SEvent& event);   
  15.   
  16.     //! This animator will receive events when attached to the active camera   
  17.     virtual bool isEventReceiverEnabled() const  
  18.     {   
  19.         return true;  // 必須爲true才能接收消息   
  20.     }   
  21.   
  22.         .... // other member functions.   
  23. private:   
  24.      ... // fields   
  25. };  
class CameraAnimator : public ISceneNodeAnimator
{
public:
	//! Constructor
	CameraAnimator(...

	//! Destructor
	virtual ~CameraAnimator(){};

	//! 更新 SceneNode 位置
    virtual void animateNode(ISceneNode* node, u32 timeMs);

	//! 處理消息
    virtual bool OnEvent(const SEvent& event);

	//! This animator will receive events when attached to the active camera
	virtual bool isEventReceiverEnabled() const
	{
		return true;  // 必須爲true才能接收消息
	}

        .... // other member functions.
private:
     ... // fields
};

 

需要實現的關鍵有三點:

(1) 拖拽時, 場景跟着移動

(2) 單擊某個圖片時,場景移動到該位置,圖片突出顯示

(3) 滾輪實現縮放

 

在編程時有以下幾個關鍵點:

(1) 三維場景中Node的選取,還好 irrlicht 替我們實現了這點,只需如下代碼,就可以將鼠標點擊處的node選取出來:

 

Cpp代碼 複製代碼 收藏代碼
  1. core::position2d<s32> mouse_position(event.MouseInput.X, event.MouseInput.Y);   
  2. ISceneNode* node = scene_manager_->getSceneCollisionManager()   
  3.         ->getSceneNodeFromScreenCoordinatesBB(mouse_position, -1);  
core::position2d<s32> mouse_position(event.MouseInput.X, event.MouseInput.Y);
ISceneNode* node = scene_manager_->getSceneCollisionManager()
        ->getSceneNodeFromScreenCoordinatesBB(mouse_position, -1);

 

(2) 運動的實現

 

    包括三個運動: CameraNode 的位置, CameraNode 的拍攝點,選取的圖片的位置

    CameraNode的位置決定了場景的移動;

    CameraNode 的拍攝點決定了拍攝的角度,可以實現場景的傾斜;

    被選取的圖片的位置可以使其比其它圖片更接近Camera,實現突出的效果。

 

    (二)中描述的圖片位置是排布在 z = 0 這個平面上的,Camera 的位置在 z = -700 平面上,初始拍攝點爲原點。

 

    camera拍攝點(target)在 X 軸移動的處理如下:

Cpp代碼 複製代碼 收藏代碼
  1. if (is_drag_ && current_node_ == NULL) // 拖拽   
  2. {   
  3.     core::position2di mouse_position = device_->getCursorControl()->getPosition();   
  4.     s32 xdiff = drag_start_point_.X - mouse_position.X;   
  5.     drag_start_point_ = mouse_position;   
  6.   
  7.     x_target += 0.01*xdiff / core::max_(dt, 0.0000001f);   
  8. }   
  9.   
  10. if (current_node_!=NULL) // 選取了某幅圖片   
  11. {   
  12.     x_target = current_node_->getPosition().X;   
  13. }   
  14.   
  15. sign = (x_target < x_current) ? -1 : 1;   
  16. x_speed = sign*sqrt(abs(x_target - x_current)) * 50;  // 運動速度   
  17. target_position.X += x_speed * dt;  // 移動位置  
if (is_drag_ && current_node_ == NULL) // 拖拽
{
	core::position2di mouse_position = device_->getCursorControl()->getPosition();
	s32 xdiff = drag_start_point_.X - mouse_position.X;
	drag_start_point_ = mouse_position;

	x_target += 0.01*xdiff / core::max_(dt, 0.0000001f);
}

if (current_node_!=NULL) // 選取了某幅圖片
{
	x_target = current_node_->getPosition().X;
}

sign = (x_target < x_current) ? -1 : 1;
x_speed = sign*sqrt(abs(x_target - x_current)) * 50;  // 運動速度
target_position.X += x_speed * dt;  // 移動位置

 

    在 Y 軸和 Z 軸方向的移動,包括圖片的移動, 均用類似的方式處理。參見(animateNode)函數。

 

(3) 鼠標滾輪實現縮放

    只需要改變 Camera 在Z軸的位置,作簡單的限幅即可:

Cpp代碼 複製代碼 收藏代碼
  1. z_target += 100*event.MouseInput.Wheel;    
  2.   
  3. if (z_target > -200)   
  4. {   
  5.     z_target = -200;   
  6. }   
  7. if (z_target < -3000)   
  8. {   
  9.     z_target = -3000;   
  10. }  
z_target += 100*event.MouseInput.Wheel; 

if (z_target > -200)
{
	z_target = -200;
}
if (z_target < -3000)
{
	z_target = -3000;
}

 

4.最後

 

最後我們還需要將實現的 animator 附加到 Camera 上:

 

Cpp代碼 複製代碼 收藏代碼
  1. scene::ICameraSceneNode* camera = scene_mgr->addCameraSceneNode(0,    
  2.     core::vector3df(0,0,-700),    
  3.     core::vector3df(0,0,0), 0);   
  4. scene::CameraAnimator* animator = new scene::CameraAnimator(wall_mgr->GetWallItemList(),    
  5.     device, scene_mgr,    
  6.     device->getTimer()->getTime());   
  7. camera->addAnimator(animator);   
  8. animator->drop();  
scene::ICameraSceneNode* camera = scene_mgr->addCameraSceneNode(0, 
    core::vector3df(0,0,-700), 
    core::vector3df(0,0,0), 0);
scene::CameraAnimator* animator = new scene::CameraAnimator(wall_mgr->GetWallItemList(), 
    device, scene_mgr, 
    device->getTimer()->getTime());
camera->addAnimator(animator);
animator->drop();

 

編譯運行後,您應該能夠用鼠標實現類似 cooliris 的交互。更進一步的完善,還需要支持鍵盤等……

 

p.s.

 

3d 界面就介紹到這裏了,也算告一段落。

還有兩個部分:

(1) GUI: 介紹GUI編寫,並加入Truetype中文字體支持;

(2) AnimatedGUI:介紹目錄瀏覽控件的編寫,實現一個皮膚系統。

 

由於臨近畢業,導師催着要論文,過段時間再繼續吧。

鑑於本人的學習經歷,勸各位千萬不要盲目讀研,不要盲目崇拜名校,別被糟蹋了。

我是學機械方向的,盲目接受了當年所謂的推研資格。sigh~~學術氣氛糟糕啊……

 

From: http://arec.iteye.com/blog/338135

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章