Irrlicht 3D Engine 筆記系列之 教程4 - Movement
作者: i_dovelemon
日期: 2014 / 12 / 16
來源: CSDN
主題: Event Receiver, Animator, Framerate independent movement and framerate dependent movement
引言
從今天開始,博主將進行對3D Engine的學習。而且,在博客中將自己學習的心得一一分享給大家。希望可以對大家有所幫助。也希望可以找到誌同道合的同伴一起學習3D 遊戲引擎方面的知識。
為什麽選擇Irrlicht?
在非常久曾經。博主就有實際研究一個引擎的想法。僅僅是一直沒有付諸行動。可是期間,也大致的了解過市場上流行的引擎。
博主希望的不是使用3D 遊戲引擎做出好玩的遊戲,而是對3D引擎內部的工作機制進行了解。所以。對於琳瑯滿目的商業和非商業開源引擎。須要從中選擇一款來進行研究是件非常困難的事情。
博主上各大論壇,問裏面的高手。對於剛開始學習的人來說,哪些引擎適合我們去學習研究。大部分的人都推薦Ogre和Irrlicht這兩個引擎。所以,我就都下載實驗一下。依照博主眼下的知識儲備和對引擎的理解能力來說。研究Irrlicht更加的適合。Irrlicht是全然使用C++開發的一款高效實時的3D渲染引擎,相對於Ogre來說。他沒有Ogre裏面那些復雜的腳本技術,對於剛開始學習的人的我來說,希望可以看到一個純粹點的引擎。假設使用腳本封裝的太多層。對初次研究的我來說,難度有點大,所以博主終於決定研究Irrlicht這款引擎。它的代碼風格組織的十分良好。而且使用的是我所常常使用的C++語言編寫,全然可以依靠眼下的知識來對Irrlicht做一些基礎性的學習和仿真研究。
關於Irrlicht
關於Irrlicht的具體信息,大家能夠到它的官方站點上去了解。也希望有很多其它的人來和博主一起研究這款引擎。以下是這個引擎的官方站點和配套的社區:
http://irrlicht.sourceforge.net/
http://www.irrlicht3d.org/
教程4--Movement
好了,廢話不多說了,進入正題吧。
在官網上。有一系列的教程來幫助我們慢慢的熟悉引擎。所以。博主也就依照這裏面提供的教程來一步一步的進行了解。
關於前面幾個教程的筆記缺失了。假設後面有時間,博主會補上這些內容。
在教程4裏面,教程向我們展示了怎樣在Irrlicht捕捉按鍵消息,而且對節點進行控制,同一時候也演示了怎樣使用Irrlicht,對一個節點施加一個動畫效果。
具體的關於這方面的代碼,大家能夠看教程4。博主在這裏就不再贅述了。
Event Receiver
在教程4中,我們了解到,想要進行對輸入輸出的處理,我們須要繼承一個接口IEventReceiver。然後在我們繼承的類裏面。復寫我們OnEvent方法,而且這種方法會在系統發生事件的時候,自己主動的被引擎所調用。這樣。我們僅僅要在這個函數裏面,對我們希望處理的消息進行處理就可以。這個就是Event Receiver主要的工作方法。為了深入的了解事件機制是怎樣工作的,我們先來看下IEventReceiver這個接口的定義怎樣:
<span style="font-family:Microsoft YaHei;">//! Interface of an object which can receive events. /** Many of the engine‘s classes inherit IEventReceiver so they are able to process events. Events usually start at a postEventFromUser function and are passed down through a chain of event receivers until OnEvent returns true. See irr::EEVENT_TYPE for a description of where each type of event starts, and the path it takes through the system. */ class IEventReceiver { public: //! Destructor virtual ~IEventReceiver() {} //! Called if an event happened. /** Please take care that you should only return ‘true‘ when you want to _prevent_ Irrlicht * from processing the event any further. So ‘true‘ does mean that an event is completely done. * Therefore your return value for all unprocessed events should be ‘false‘. \return True if the event was processed. */ virtual bool OnEvent(const SEvent& event) = 0; };</span>
這是Irrlicht引擎中關於IEventReceiver接口的定義。上面另一段描寫敘述。解釋例如以下:
引擎中非常多類都繼承這個接口,所以他們都擁有處理事件的能力。
事件一般是由一個postEventFromUser函數來觸發的。而且在觸發之後,依照一條責任鏈條依次傳遞下去。直到OnEvent函數返回true時結束。
具體了解每個事件從何處產生,而且責任鏈是怎樣的請看EEVENT_TYPE這個類型的描寫敘述。
在這個接口裏面,僅僅有一個純虛擬函數OnEvent。也就是在對象收到消息的時候,進行消息處理的唯一的函數了。對這個函數的解釋例如以下所看到的:
當你想要將消息繼續傳遞下去的時候,那麽就在這個函數的末尾返回false。當你想要終止消息的傳遞的時候,請返回true。
這個描寫敘述,告訴了我們應該怎麽樣終止責任鏈和怎樣繼續沿著責任鏈傳遞消息下去。
當博主看到這裏的時候,有個疑問。我們繼承這個接口實現的一個事件接受器,如教程4中所看到的。它處在責任鏈的哪一個部分了?是最開始進行處理的?還是最後進行處理的了?
針對這個疑問。博主,查看了EEVENT_TYPE中責任鏈的描寫敘述。原來,在Irrlicht中,用戶定義的事件接收器在不同的情況下,所處的位置實際是不同的。可是大致能夠分為例如以下幾種情況,這些情況都具體的描寫敘述在EEVENT_TYPE中了。我將這段描寫敘述拷貝下來。例如以下所看到的:
<span style="font-family:Microsoft YaHei;"> //! Enumeration for all event types there are. enum EEVENT_TYPE { //! An event of the graphical user interface. /** GUI events are created by the GUI environment or the GUI elements in response to mouse or keyboard events. When a GUI element receives an event it will either process it and return true, or pass the event to its parent. If an event is not absorbed before it reaches the root element then it will then be passed to the user receiver. */ EET_GUI_EVENT = 0, //! A mouse input event. /** Mouse events are created by the device and passed to IrrlichtDevice::postEventFromUser in response to mouse input received from the operating system. Mouse events are first passed to the user receiver, then to the GUI environment and its elements, then finally the input receiving scene manager where it is passed to the active camera. */ EET_MOUSE_INPUT_EVENT, //! A key input event. /** Like mouse events, keyboard events are created by the device and passed to IrrlichtDevice::postEventFromUser. They take the same path as mouse events. */ EET_KEY_INPUT_EVENT, //! A joystick (joypad, gamepad) input event. /** Joystick events are created by polling all connected joysticks once per device run() and then passing the events to IrrlichtDevice::postEventFromUser. They take the same path as mouse events. Windows, SDL: Implemented. Linux: Implemented, with POV hat issues. MacOS / Other: Not yet implemented. */ EET_JOYSTICK_INPUT_EVENT, //! A log event /** Log events are only passed to the user receiver if there is one. If they are absorbed by the user receiver then no text will be sent to the console. */ EET_LOG_TEXT_EVENT, //! A user event with user data. /** This is not used by Irrlicht and can be used to send user specific data though the system. The Irrlicht ‘window handle‘ can be obtained from IrrlichtDevice::getExposedVideoData() The usage and behavior depends on the operating system: Windows: send a WM_USER message to the Irrlicht Window; the wParam and lParam will be used to populate the UserData1 and UserData2 members of the SUserEvent. Linux: send a ClientMessage via XSendEvent to the Irrlicht Window; the data.l[0] and data.l[1] members will be casted to s32 and used as UserData1 and UserData2. MacOS: Not yet implemented */ EET_USER_EVENT, //! This enum is never used, it only forces the compiler to //! compile these enumeration values to 32 bit. EGUIET_FORCE_32_BIT = 0x7fffffff };</span>從上面的描寫敘述,能夠看到,Irrlicht引擎,將事件的類型分為6大不同的基礎類型,而每一種基礎類型都擁有自己的責任鏈傳遞方式。我們來一一了解下。
第一種是EET_GUI_EVENT。也就是GUI事件消息。這樣的事件消息是由GUI環境和GUI元素在響應按鍵或者鼠標時所產生的。
當GUI元素接受到這個事件的時候,要麽處理它然後返回true,要麽就是將該事件傳遞給GUI元素的父節點,直到傳遞到根節點為止。假設在傳遞到根節點之後,依舊沒有被拋棄。那麽就會調用用戶定義的事件接收器來對消息進行處理。
另外一種是EET_MOUSE_INPUT_EVENT,也就是鼠標輸入事件。
鼠標事件是由設備產生。而且傳遞給IrrlichtDevice::postEventFromUser函數來對操作系統的消息進行響應。鼠標事件首先傳遞到用戶定義的事件接受器中,然後傳遞到GUI環境和它的GUI元素節點中,最後在傳遞到場景管理器中,傳遞給當前活躍的相機,來進行處理。
第三種是EET_KEY_INPUT_EVENT,也就是鍵盤按鍵事件。相同的。這個事件也是有機器設備產生的,然後傳遞給了IrrlichtDevice::postEventFromUser來進行響應。和上面鼠標事件共享相同的責任鏈。
第四種是EET_JOYSTICK_INPUT_EVENT。也就是手柄事件。同上面鼠標和鍵盤一樣。通過IrrlichtDevice::postEventFromUser來傳遞,然後經過相同的責任鏈進行事件的處理。
第五種是EET_LOG_TEXT_EVENT,也就是日誌事件。日誌事件只傳遞給用戶定義的事件接受器。假設在用戶定義的事件接收器中,將這些消息拋棄了,那麽將不會在控制臺下輸出日誌信息。
第六種是EET_USER_EVENT。也就是用戶自己定義的事件。
這樣的類型的消息。Irrlicht並不使用,而只將Irrlicht當成是事件中轉站來傳遞事件。
以上六種基本情況,就是Irrlicht引擎所支持的事件處理機制的全部情況了。
事件處理機制。除了產生和傳遞這種基本條件之外。另外一個十分重要的內容就是事件本身的定義。
在Irrlicht中。事件的定義是由例如以下的結構體所定義的:
<span style="font-family:Microsoft YaHei;">//! SEvents hold information about an event. See irr::IEventReceiver for details on event handling. struct SEvent { //! Any kind of GUI event. struct SGUIEvent { //! IGUIElement who called the event gui::IGUIElement* Caller; //! If the event has something to do with another element, it will be held here. gui::IGUIElement* Element; //! Type of GUI Event gui::EGUI_EVENT_TYPE EventType; }; //! Any kind of mouse event. struct SMouseInput { //! X position of mouse cursor s32 X; //! Y position of mouse cursor s32 Y; //! mouse wheel delta, often 1.0 or -1.0, but can have other values < 0.f or > 0.f; /** Only valid if event was EMIE_MOUSE_WHEEL */ f32 Wheel; //! True if shift was also pressed bool Shift:1; //! True if ctrl was also pressed bool Control:1; //! A bitmap of button states. You can use isButtonPressed() to determine //! if a button is pressed or not. //! Currently only valid if the event was EMIE_MOUSE_MOVED u32 ButtonStates; //! Is the left button pressed down?bool isLeftPressed() const { return 0 != ( ButtonStates & EMBSM_LEFT ); } //! Is the right button pressed down? bool isRightPressed() const { return 0 != ( ButtonStates & EMBSM_RIGHT ); } //! Is the middle button pressed down?
bool isMiddlePressed() const { return 0 != ( ButtonStates & EMBSM_MIDDLE ); } //! Type of mouse event EMOUSE_INPUT_EVENT Event; }; //! Any kind of keyboard event. struct SKeyInput { //! Character corresponding to the key (0, if not a character) wchar_t Char; //! Key which has been pressed or released EKEY_CODE Key; //! If not true, then the key was left up bool PressedDown:1; //! True if shift was also pressed bool Shift:1; //! True if ctrl was also pressed bool Control:1; }; //! A joystick event. /** Unlike other events, joystick events represent the result of polling * each connected joystick once per run() of the device. Joystick events will * not be generated by default. If joystick support is available for the * active device, _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ is defined, and * @ref irr::IrrlichtDevice::activateJoysticks() has been called, an event of * this type will be generated once per joystick per @ref IrrlichtDevice::run() * regardless of whether the state of the joystick has actually changed. */ struct SJoystickEvent { enum { NUMBER_OF_BUTTONS = 32, AXIS_X = 0, // e.g. analog stick 1 left to right AXIS_Y, // e.g. analog stick 1 top to bottom AXIS_Z, // e.g. throttle, or analog 2 stick 2 left to right AXIS_R, // e.g. rudder, or analog 2 stick 2 top to bottom AXIS_U, AXIS_V, NUMBER_OF_AXES }; /** A bitmap of button states. You can use IsButtonPressed() to ( check the state of each button from 0 to (NUMBER_OF_BUTTONS - 1) */ u32 ButtonStates; /** For AXIS_X, AXIS_Y, AXIS_Z, AXIS_R, AXIS_U and AXIS_V * Values are in the range -32768 to 32767, with 0 representing * the center position. You will receive the raw value from the * joystick, and so will usually want to implement a dead zone around * the center of the range. Axes not supported by this joystick will * always have a value of 0. On Linux, POV hats are represented as axes, * usually the last two active axis. */ s16 Axis[NUMBER_OF_AXES]; /** The POV represents the angle of the POV hat in degrees * 100, * from 0 to 35,900. A value of 65535 indicates that the POV hat * is centered (or not present). * This value is only supported on Windows. On Linux, the POV hat * will be sent as 2 axes instead. */ u16 POV; //! The ID of the joystick which generated this event. /** This is an internal Irrlicht index; it does not map directly * to any particular hardware joystick. */ u8 Joystick; //! A helper function to check if a button is pressed. bool IsButtonPressed(u32 button) const { if(button >= (u32)NUMBER_OF_BUTTONS) return false; return (ButtonStates & (1 << button)) ? true : false; } }; //! Any kind of log event. struct SLogEvent { //! Pointer to text which has been logged const c8* Text; //! Log level in which the text has been logged ELOG_LEVEL Level; }; //! Any kind of user event. struct SUserEvent { //! Some user specified data as int s32 UserData1; //! Another user specified data as int s32 UserData2; }; EEVENT_TYPE EventType; union { struct SGUIEvent GUIEvent; struct SMouseInput MouseInput; struct SKeyInput KeyInput; struct SJoystickEvent JoystickEvent; struct SLogEvent LogEvent; struct SUserEvent UserEvent; }; };</span>
Irrlicht引擎,為了使事件定義可以通用與上面所定義的6中基本事件類型,它將這6種事件都定義在SEvent這個結構中了。除此之外,它還加上一個成員,用於標示該事件確切的屬於哪一種情況,也就是上面的EventType屬性了。
關於每一種類型確切的結構,將在以後使用遇到的時候具體的研究,這裏僅僅從整體架構上面來分析下。
從上面關於事件機制的分析中,博主學到了幾樣東西,分享一下給大家:
1.事件機制須要有事件產生方式,和事件傳遞路徑。以及事件本身定義來進行構造
2.事件的產生,往往依賴於特定功能的實現,也是對特定功能的響應操作。
3.事件的傳遞路徑,能夠使用設計模式中的責任鏈的方法來進行設計,便於事件進行層級處理
4.在設計一個系統時,可能各個子系統的傳遞路徑並不同樣,我們不能如果他們傳遞路徑是同樣的,最好可以讓子系統自定義自己的傳遞路徑
5.一個事件處理系統,除了內置的事件處理功能之外,最好可以讓用戶定義自己的事件處理器,而且在傳遞路徑上要可以讓用戶決定傳遞是否結束。
以上就是博主研究分析Irrlicht引擎。所獲取的關於事件處理機制的知識。
Animator
在教程4中,演示了使用Irrlicht的Animator特性,來制造一些內置的動畫效果。這些動畫效果都是通過ISceneManager這個結構來創建一個繼承ISceneNodeAnimator接口的對象來實現的。所以。有必要對引擎的這個特性進行一下分析。我們首先來看下。Irrlicht可以創建出哪些Animator,以下是在ISceneManager接口中定義的關於創建Animator的全部接口函數:
<span style="font-family:Microsoft YaHei;"> //! Creates a rotation animator, which rotates the attached scene node around itself. /** \param rotationSpeed Specifies the speed of the animation in degree per 10 milliseconds. \return The animator. Attach it to a scene node with ISceneNode::addAnimator() and the animator will animate it. If you no longer need the animator, you should call ISceneNodeAnimator::drop(). See IReferenceCounted::drop() for more information. */ virtual ISceneNodeAnimator* createRotationAnimator(const core::vector3df& rotationSpeed) = 0; //! Creates a fly circle animator, which lets the attached scene node fly around a center. /** \param center: Center of the circle. \param radius: Radius of the circle. \param speed: The orbital speed, in radians per millisecond. \param direction: Specifies the upvector used for alignment of the mesh. \param startPosition: The position on the circle where the animator will begin. Value is in multiples of a circle, i.e. 0.5 is half way around. (phase) \param radiusEllipsoid: if radiusEllipsoid != 0 then radius2 froms a ellipsoid begin. Value is in multiples of a circle, i.e. 0.5 is half way around. (phase) \return The animator. Attach it to a scene node with ISceneNode::addAnimator() and the animator will animate it. If you no longer need the animator, you should call ISceneNodeAnimator::drop(). See IReferenceCounted::drop() for more information. */ virtual ISceneNodeAnimator* createFlyCircleAnimator( const core::vector3df& center=core::vector3df(0.f,0.f,0.f), f32 radius=100.f, f32 speed=0.001f, const core::vector3df& direction=core::vector3df(0.f, 1.f, 0.f), f32 startPosition = 0.f, f32 radiusEllipsoid = 0.f) = 0; //! Creates a fly straight animator, which lets the attached scene node fly or move along a line between two points. /** \param startPoint: Start point of the line. \param endPoint: End point of the line. \param timeForWay: Time in milli seconds how long the node should need to move from the start point to the end point. \param loop: If set to false, the node stops when the end point is reached. If loop is true, the node begins again at the start. \param pingpong Flag to set whether the animator should fly back from end to start again. \return The animator. Attach it to a scene node with ISceneNode::addAnimator() and the animator will animate it. If you no longer need the animator, you should call ISceneNodeAnimator::drop(). See IReferenceCounted::drop() for more information. */ virtual ISceneNodeAnimator* createFlyStraightAnimator(const core::vector3df& startPoint, const core::vector3df& endPoint, u32 timeForWay, bool loop=false, bool pingpong = false) = 0; //! Creates a texture animator, which switches the textures of the target scene node based on a list of textures. /** \param textures: List of textures to use. \param timePerFrame: Time in milliseconds, how long any texture in the list should be visible. \param loop: If set to to false, the last texture remains set, and the animation stops. If set to true, the animation restarts with the first texture. \return The animator. Attach it to a scene node with ISceneNode::addAnimator() and the animator will animate it. If you no longer need the animator, you should call ISceneNodeAnimator::drop(). See IReferenceCounted::drop() for more information. */ virtual ISceneNodeAnimator* createTextureAnimator(const core::array<video::ITexture*>& textures, s32 timePerFrame, bool loop=true) = 0; //! Creates a scene node animator, which deletes the scene node after some time automatically. /** \param timeMs: Time in milliseconds, after when the node will be deleted. \return The animator. Attach it to a scene node with ISceneNode::addAnimator() and the animator will animate it. If you no longer need the animator, you should call ISceneNodeAnimator::drop(). See IReferenceCounted::drop() for more information. */ virtual ISceneNodeAnimator* createDeleteAnimator(u32 timeMs) = 0; //! Creates a special scene node animator for doing automatic collision detection and response. /** See ISceneNodeAnimatorCollisionResponse for details. \param world: Triangle selector holding all triangles of the world with which the scene node may collide. You can create a triangle selector with ISceneManager::createTriangleSelector(); \param sceneNode: SceneNode which should be manipulated. After you added this animator to the scene node, the scene node will not be able to move through walls and is affected by gravity. If you need to teleport the scene node to a new position without it being effected by the collision geometry, then call sceneNode->setPosition(); then animator->setTargetNode(sceneNode); \param ellipsoidRadius: Radius of the ellipsoid with which collision detection and response is done. If you have got a scene node, and you are unsure about how big the radius should be, you could use the following code to determine it: \code const core::aabbox3d<f32>& box = yourSceneNode->getBoundingBox(); core::vector3df radius = box.MaxEdge - box.getCenter(); \endcode \param gravityPerSecond: Sets the gravity of the environment, as an acceleration in units per second per second. If your units are equivalent to metres, then core::vector3df(0,-10.0f,0) would give an approximately realistic gravity. You can disable gravity by setting it to core::vector3df(0,0,0). \param ellipsoidTranslation: By default, the ellipsoid for collision detection is created around the center of the scene node, which means that the ellipsoid surrounds it completely. If this is not what you want, you may specify a translation for the ellipsoid. \param slidingValue: DOCUMENTATION NEEDED. \return The animator. Attach it to a scene node with ISceneNode::addAnimator() and the animator will cause it to do collision detection and response. If you no longer need the animator, you should call ISceneNodeAnimator::drop(). See IReferenceCounted::drop() for more information. */ virtual ISceneNodeAnimatorCollisionResponse* createCollisionResponseAnimator( ITriangleSelector* world, ISceneNode* sceneNode, const core::vector3df& ellipsoidRadius = core::vector3df(30,60,30), const core::vector3df& gravityPerSecond = core::vector3df(0,-10.0f,0), const core::vector3df& ellipsoidTranslation = core::vector3df(0,0,0), f32 slidingValue = 0.0005f) = 0; //! Creates a follow spline animator. /** The animator modifies the position of the attached scene node to make it follow a hermite spline. It uses a subset of hermite splines: either cardinal splines (tightness != 0.5) or catmull-rom-splines (tightness == 0.5). The animator moves from one control point to the next in 1/speed seconds. This code was sent in by Matthias Gall. If you no longer need the animator, you should call ISceneNodeAnimator::drop(). See IReferenceCounted::drop() for more information. */ virtual ISceneNodeAnimator* createFollowSplineAnimator(s32 startTime, const core::array< core::vector3df >& points, f32 speed = 1.0f, f32 tightness = 0.5f, bool loop=true, bool pingpong=false) = 0;</span>
從上面的函數接口中,能夠看到Irrlicht支持例如以下的幾種Animator:
RotationAnimator -- 創建一個環繞自身進行旋轉的Animator
FlyCircleAnimator -- 創建一個環繞指定中心進行旋轉的Animator(教程4中就是使用這個Animator)
FlyStraightAnimator -- 創建一個沿著兩點進行移動的Animator
TextureAnimator -- 創建一個紋理Animator
DeleteAnimator -- 創建一個刪除Animator。用於隨著時間來漸進的刪除節點的Animator
CollisionResponseAnimator -- 創建一個進行碰撞檢測和反應的Animator
FollowSplineAnimator -- 創建一個尾隨的Animator
從上面可以看到,Irrlicht對於Animator的特性支持的不是非常多。博主所熟悉的一款2D遊戲引擎cocos2d-x對於這種Animator特性的支持就非常的好。
所不同的是在cocos2d-x中,這個特性叫做Action。
cocos2d-x中對Action的支持非常的好。可以通過動作之間的組合,延遲等等做出非常復雜的Action操作出來。希望以後的Irrlicht版本號可以添加這種特性,這樣就更加方便遊戲開發人員來進行遊戲開發工作了。
那麽,沒有cocos2d-x中的Action特性,我們也想實現那些特性怎麽辦了?博主眼下所知道的方法就是用戶們自己繼承ISceneNodeAnimator,來創建獨立於Irrlicht引擎的Animator,這樣就行從一定程度上擴展Irrlicht引擎關於Animator特性的支持了。那麽。要可以創建出Animator,我們須要對ISceneNodeAnimator這個接口十分的了解才行,所以我們先來了解下ISceneNodeAnimator這個接口。以下是ISceneNodeAnimator接口的完整定義:
<span style="font-family:Microsoft YaHei;"> //! Animates a scene node. Can animate position, rotation, material, and so on. /** A scene node animator is able to animate a scene node in a very simple way. It may change its position, rotation, scale and/or material. There are lots of animators to choose from. You can create scene node animators with the ISceneManager interface. */ class ISceneNodeAnimator : public io::IAttributeExchangingObject, public IEventReceiver { public: //! Animates a scene node. /** \param node Node to animate. \param timeMs Current time in milli seconds. */ virtual void animateNode(ISceneNode* node, u32 timeMs) =0; //! Creates a clone of this animator. /** Please note that you will have to drop (IReferenceCounted::drop()) the returned pointer after calling this. */ virtual ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager=0) =0; //! Returns true if this animator receives events. /** When attached to an active camera, this animator will be able to respond to events such as mouse and keyboard events. */ virtual bool isEventReceiverEnabled() const { return false; } //! Event receiver, override this function for camera controlling animators virtual bool OnEvent(const SEvent& event) { return false; } //! Returns type of the scene node animator virtual ESCENE_NODE_ANIMATOR_TYPE getType() const { return ESNAT_UNKNOWN; } //! Returns if the animator has finished. /** This is only valid for non-looping animators with a discrete end state. \return true if the animator has finished, false if it is still running. */ virtual bool hasFinished(void) const { return false; } };</span>
一開始看這個接口,我們就行發現,這個接口繼承至一個IEventReceiver,也就是說Animator可以接受事件,而且進行事件處理。我們在來看下它內部有哪些個函數。
函數: virtual void animateNode(ISceneNode* node, u32 timeMs) = 0 ;
這個函數就是用來對node進行Animator操作的函數。它具有須要進行Animator的節點的對象node,以及進行Animator的時間timeMs等。
也就是說,實際的對節點的操作就是在這裏進行的。
函數:virtual ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager=0) = 0 ;
這個函數用於創建當前Animator的一個克隆物。
函數:virtual bool isEventReceiverEnabled() const ;
用於推斷當前的Animator是否可以接受事件。
函數: virtual bool hasFinished(void) const ;
用於推斷非循環的Animator是否結束的函數。
在大致的了解了這個接口之後,我們知道,想要實現自己的Animator,只要繼承該接口。而且復寫裏面的函數animateNode函數就能夠了。
可是這不過大體上理解,具體情況究竟怎樣了?我們來實際的看下Irrlicht引擎內部的Animator的animateNode函數是怎樣編寫的。就拿本教程的FlyCircleAnimator來說吧。
在源碼中,找尋FlyCircleAnimator的申明,例如以下所看到的:
<span style="font-family:Microsoft YaHei;">namespace scene { class CSceneNodeAnimatorFlyCircle : public ISceneNodeAnimator { public: //! constructor CSceneNodeAnimatorFlyCircle(u32 time, const core::vector3df& center, f32 radius, f32 speed, const core::vector3df& direction, f32 radiusEllipsoid); //! animates a scene node virtual void animateNode(ISceneNode* node, u32 timeMs); //! Writes attributes of the scene node animator. virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const; //! Reads attributes of the scene node animator. virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0); //! Returns type of the scene node animator virtual ESCENE_NODE_ANIMATOR_TYPE getType() const { return ESNAT_FLY_CIRCLE; } //! Creates a clone of this animator. /** Please note that you will have to drop (IReferenceCounted::drop()) the returned pointer after calling this. */ virtual ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager=0); private: // do some initial calculations void init(); // circle center core::vector3df Center; // up-vector, normal to the circle‘s plane core::vector3df Direction; // Two helper vectors core::vector3df VecU; core::vector3df VecV; f32 Radius; f32 RadiusEllipsoid; f32 Speed; u32 StartTime; }; } // end namespace scene</span>
從這個類的申明中能夠發現,該類復寫了接口的animateNode方法以及createClone方法,而且在內部定義了用於完畢自己Animator任務的成員屬性。好了,我麽來看下animateNode和createClone方法的實現怎樣:
<span style="font-family:Microsoft YaHei;">//! animates a scene node void CSceneNodeAnimatorFlyCircle::animateNode(ISceneNode* node, u32 timeMs) { if ( 0 == node ) return; f32 time; // Check for the condition where the StartTime is in the future. if(StartTime > timeMs) time = ((s32)timeMs - (s32)StartTime) * Speed; else time = (timeMs-StartTime) * Speed; // node->setPosition(Center + Radius * ((VecU*cosf(time)) + (VecV*sinf(time)))); f32 r2 = RadiusEllipsoid == 0.f ? Radius : RadiusEllipsoid; node->setPosition(Center + (Radius*cosf(time)*VecU) + (r2*sinf(time)*VecV ) ); }</span>
<span style="font-family:Microsoft YaHei;">ISceneNodeAnimator* CSceneNodeAnimatorFlyCircle::createClone(ISceneNode* node, ISceneManager* newManager) { CSceneNodeAnimatorFlyCircle * newAnimator = new CSceneNodeAnimatorFlyCircle(StartTime, Center, Radius, Speed, Direction, RadiusEllipsoid); return newAnimator; }</span>
分析下。這兩個函數。不是非常復雜。而可以讓節點運動的函數就是animateNode方法了,在這個函數裏面,它首先檢查了節點是否為空,然後計算經過的時間,最後改變節點在圓上的位置。而clone函數。就是簡單的將對象又一次創建一遍而已。
好了,至此。我們大概了解了要實現一個Animator須要的內容。
明天,博主將動手實際的實現一個自己的Animator,畢竟僅僅有動手之後才知道理解的是否是正確的。
從對Irrlicht引擎的Animator特性的研究中,能夠學到例如以下的幾個內容:
1.將Animator與Node設計進行分離,比較類似策略模式,可以讓Node選擇使用哪一種Animator。而且Animator也與須要進行動畫的Node進行解耦
2.開發共用的動畫接口。能夠讓用戶自由的實現自己的Animator。
因為Irrlicht的Animator設計的是在太過簡單,以後研究下cocos2d-x的Action機制,試試看可以將該特性移植到Irrlicht中來。這樣對於以後進行遊戲開發將很的easy。
Framerate Independent and Framerate dependent
在教程4的最後一段內容,了解到在遊戲開發中,控制移動是有兩種不同的方式的。一種被稱為Framerate Independent的控制方式,也就是與幀率無關的移動控制方法,換句話說,就是使用時間來控制移動。
比方,當我們的遊戲。因為CPU資源過於緊張而導致幀率下降。假設使用幀率無關的控制方式,那麽就會發現無論幀率下降與否,人物在1s的時間內,移動的距離都是一樣的。
而除了Framerate Independent的方式之外,就是Framerate dependent的控制方式了。這樣的控制方式,表示的意思是不是依據時間來控制移動,而是在每一幀的間隔裏面都移動同樣的距離。這樣的移動情況會受到幀率的影響,假設幀率下降的非常多,那麽人物的移動就會變的奇慢無比。
那麽是不是說。第一種方式就是比另外一種方式好了?也不是。想想看,假設因為你在某一幀的時候,遊戲讀取某個大數據文件。導致在這幀的時候,出現卡幀的現象,也就是在大於1s的時間。沒有進行換幀操作。那麽當接下來進行換幀之後,實際上僅僅是經過了1幀。可是時間上卻超過了1s。假設此時遊戲是Framerate Independent的,就會出現人物突然跳躍的情況。這樣的情況在網絡遊戲中常常遇到。博主希望打lol,常常在網絡延遲的時候,因為幀之間接受數據的時間大於1s。導致接下來的一幀突然跳躍到對方陣營中。從而無情的被敵方蹂躪。
所以,以上兩種方法。各有優缺點。讀者們最好依據情況來分析,你須要使用哪一種來進行。假設可以糅合使用這兩種。而且可以依據情況進行切換。是否可以避免出現博主被敵方蹂躪的現象了???期待大家可以想出結合這兩種方式長處的解決方式出來!!
!
Irrlicht 3D Engine 筆記系列之 教程4 - Movement