返回> 网站首页 

Irrlicht 游戏引擎开发 cooliris 界面(三)

yoours2011-01-24 17:04:58 阅读 1421

简介一边听听音乐,一边写写文章。

(三)着重于介绍如何让场景动起来,以及如何获取和处理消息。

源码: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();  

 

我们所要做的,就是在 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. }  

 

也就是说,只有当前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. };  

 

需要实现的关键有三点:

(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);  

 

(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;  // 移动位置  

 

    在 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. }  

 

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();  

 

编译运行后,您应该能够用鼠标实现类似 cooliris 的交互。更进一步的完善,还需要支持键盘等……

 

p.s.

 

3d 界面就介绍到这里了,也算告一段落。

还有两个部分:

(1) GUI: 介绍GUI编写,并加入Truetype中文字体支持;

(2) AnimatedGUI:介绍目录浏览控件的编写,实现一个皮肤系统。

 


 

微信小程序扫码登陆

文章评论

1421人参与,0条评论