Irrlicht学习备忘录——4 Movement

来源:互联网 发布:java中的反射及其作用 编辑:程序博客网 时间:2024/05/09 20:08

4 Movement

官方代码($sdk)\examples\04.Movement


这个例子讲的是如何使场景节点动起来。

我在学习了官方例子,并仔细看了irr源码后,个人觉着使irr场景节点动起来的方法,严格的说就是官方例子里的两种方法。第一种是直接操纵场景节点ISceneNode调整位置;第二种是通过ISceneNodeAnimator间接操作场景节点ISceneNode调整位置。但似乎官方教程只是为了给大家清楚irr能干什么,并没有对irr进行更深入的讲解。从源码里可以看出,ISceneNodeAnimator并不仅仅是irr内部使用的接口,也是给使用者扩展的接口。通过自己扩展ISceneNodeAnimator,同样也能够操作ISceneNode位置,但这种方法说严格了,也是例子里的方法,只是它没教如何做扩展。

直接操纵场景节点ISceneNode调整位置,这在前面的例子里已经使用过,只是对每个节点初始化时使用一次setPosition方法,将场景节点放到预定的位置,因此场景节点就是静态的,并没有动起来。每隔一段时间,就使用一次setPosition方法将场景节点放到不同的位置,场景节点就成动起来的了。按这样说,使场景节点动起来很简单嘛,只需要在前面例子中的循环内对场景节点的位置进行重新设置就行了。想法虽然没错,但如果场景节点很多很多,而且每个场景节点的运动方法都不一样,这方法还行得通吗?我自认水平低,我可没这水平管理好那么多节点做不同的运动。irr提供的ISceneNodeAnimator就是一个用来自动控制场景节点运动的控制器。在场景节点上添加一个ISceneNodeAnimator后,这个场景节点的运动就受ISceneNodeAnimator控制了。用ISceneNodeAnimator来控制很多很多的场景节点运动,我觉着总比全在循环内对每一个场景节点进行位置重设要简单的多吧。

场景节点的运动,也总不能全由程序控制吧,部分节点的运动还是需要输入设备,如鼠标、键盘、手柄等来控制才有意思嘛。这要怎么做呢?irr本身就是游戏引擎,游戏能不支持输入设备吗?既然游戏需要输入设备,那游戏引擎也就支持输入设备。irr里提供了IEventReciver基类用来从消息循环里接收用户需要的输入设备的输入消息。irr支持的输入设备有键盘、鼠标和手柄(游戏操作杆),只需要自己派生一个IEventReciver类,将自己需要的输入设备消息提取出来,就可以使用这些设备了。要使用体感,就自己动脑筋去扩展吧,irr1.8里还没有体感的支持。

下面来具体看看irr移动场景节点的教程。

首先,为了获取用户鼠标键盘输入事件,以及GUI的各类事件(例如,XX按钮被按下)并对其进行相应的处理,教程里创建了一个EventReceiver事件接受器的实体对象,它继承于IEventReciver类。继承IEventReciver类是必须的,这个类是个抽象类,除非不打算使用它。

在头文件IEventReciver.h里,能看的它的全貌。

classIEventReceiver

{

public:

virtual~IEventReceiver() {}

virtualbool OnEvent(const SEvent& event) = 0;

};

非常的简单,只需要重写一个OnEvent方法就行了。SEvent就是irr消息循环里的消息事件,在同一个头文件里有它的结构。irr的消息事件种类不多,有EET_GUI_EVENT图形界面事件、EET_MOUSE_INPUT_EVENT鼠标输入事件、EET_KEY_INPUT_EVENT键盘输入事件、EET_JOYSTICK_INPUT_EVENT游戏操作杆事件、EET_LOG_TEXT_EVENT标志文字事件、EET_USER_EVENT用户事件。在例子中只使用了键盘输入事件。为了记录键盘按键状态,在派生的事件接受器MyEventReceiver类里使用了一个数组来存储按键状态。例子里获取按键状态并没有直接从数组里取,而是增加了个方法来操作,这种方式虽然效率不高,使用麻烦,但用起来这种封装方式很安全,不容易因疏忽编写出访问数组越界的代码,值得学习。

classMyEventReceiver : public IEventReceiver

{

public:

virtualbool OnEvent(const SEvent& event)

{

获取键盘输入消息,将产生该消息的按键状态进行修改

if(event.EventType == irr::EET_KEY_INPUT_EVENT)

KeyIsDown[event.KeyInput.Key]= event.KeyInput.PressedDown;

returnfalse;

}

例子中增加的方法,获取按键是否按下。

virtualbool IsKeyDown(EKEY_CODE keyCode) const

{

returnKeyIsDown[keyCode];

}

对按键状态进行初始化

MyEventReceiver()

{

for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)

KeyIsDown[i]= false;

}

private:

保存按键状态的数组

boolKeyIsDown[KEY_KEY_CODES_COUNT];

};

派生的MyEventReceiver类写好后,怎么用呢?这里的确跟前面的例子有不同了。前面的例子中,创建irr设备时,createDevice最后一个参数是0,这里需要修改成MyEventReceiver对象的指针。

MyEventReceiverreceiver;

IrrlichtDevice*device = createDevice(driverType,core::dimension2d<s32>(640,480), 16, false, false, false, &receiver);

看起来在创建irr设备时就得设置IEventReceiver事件接收器,似乎设计IEventReceiver派生类就不能太简单了。设计的简单,以后功能不够用时又得改,而且用户界面事件也在里面,操作界面太多的话,这个派生类也会设计的很臃肿,这类不容易设计啊。实际上不是这样的,irr设备提供了一个setEventReceiver方法,用来重新设置派生的事件接收器,还提供了一个getEventReceiver方法来获取当前使用的事件接收器,只需要设计一个满足当时需要的事件接收器就行了,场景或界面变化后,重新设置一个相符的事件接收器就可以了。

例子后面的代码,基本跟前面的例子差不多,创建了一个球、一个立方体。还有一个动画模型,三种场景节点。对这三个场景节点,球采用在循环里直接设置场景节点位置的方法来控制运动;立方体和动画模型使用ISceneNodeAnimator来自动控制场景节点的运动。

在创建立方体的代码后,有ISceneNodeAnimator*anim = smgr->createFlyCircleAnimator(core::vector3df(0,0,30),20.0f),创建了一个绕圈转的运动控制器,使用立方体场景节点的addAnimator(anim)将改运动控制器添加到了立方体场景节点。这样立方体场景节点就会在运动控制器的控制下,围绕着(0030)位置,以半径20绕圈。

在创建动画模型节点的代码后,有ISceneNodeAnimator*anim=smgr->createFlyStraightAnimator(core::vector3df(100,0,60),core::vector3df(-100,0,60),3500,true),创建了一个直线运动的运动控制器,同样使用addAnimator(anim)将改运动控制器添加到场景节点里,这样这个动画模型节点就会不断的用3500毫秒从位置A(100,0,60)移动到位置B(-100,0,60)

在循环部分,在绘图代码前,通过使用device->getTimer()->getTime()获取irr设备当前时间,计算出当前帧和上一帧的时间间隔,再通过事件接收器获取WSAD四个键的状态和球体移动的速度,算出当前帧球体场景节点移动到什么位置,最后使用setPosition重新设置球体场景节点的位置。这样每显示一帧图像,球体的位置都会根据按键情况进行调整,球体也就成了手动控制移动的场景节点。

irr官方教程教的就这么多,再深入的扩展ISceneNodeAnimator的方法没讲,我同样准备把扩展单独写。

原创粉丝点击