Irrlicht 3D Engine 笔记系列 之 自己定义Animator
作者: i_dovelemon
日期: 2014 / 12 / 17
來源: CSDN
主題: Custom Animator, Referenced count
引言
在昨天的文章《Irrlicht 3D Engine 筆記系列 之 教程4 - Movement》中,博主向大家保證會在今天向大家實際操作怎樣擴展Irrlicht引擎的Animator特性。假設(shè)讀者對Irrlicht的Animator的特性不是非常了解的話,請先了解下前面的那篇文章,本片文章是在上次文章的基礎(chǔ)上進(jìn)行的。
Custom Animator
經(jīng)過昨天我們的分析,發(fā)現(xiàn)僅僅要我們繼承ISceneNodeAnimator這個接口來實現(xiàn)我們自己的Animator就能夠了。所以以下,我們就按著這個思路來對創(chuàng)建我們自己定義的Animator。
首先,對我們將要創(chuàng)建的Animator進(jìn)行下簡單的描寫敘述:
博主想要創(chuàng)建一個沿著某條直線進(jìn)行循環(huán)運動,而且物體本身在旋轉(zhuǎn)的Animator。博主稱之為Roll Animator(翻滾動畫)。
好了。在明白了我們想要創(chuàng)建的Animator的功能之后。我們就實際動手來創(chuàng)建一個。
首先,我們來申明一個CMyRollAnimator的類,這個類繼承至ISceneNodeAnimator,而且復(fù)寫當(dāng)中的兩個純虛擬函數(shù)animateNode和createClone,申明頭文件例如以下所看到的:
//-------------------------------------------------------------------------------------------------
// declaration : Copyright (c), by XJ , 2014. All right reserved .
// brief : This file will define the custom roll animator.
// author : XJ
// date : 2014 / 12 / 17
// version : 1.0
//--------------------------------------------------------------------------------------------------
#pragma once #include<irrlicht.h>
using namespace irr ;
using namespace scene ;
using namespace core; class CMyRollAnimator: public ISceneNodeAnimator
{
public:
CMyRollAnimator(vector3df _startPos, vector3df _endPos,
f32 _speed); //animate a node
virtual void animateNode(ISceneNode* node, u32 timeMs); //clone a animator
virtual ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager=0); private:
vector3df m_vStartPos ;
vector3df m_vEndPos ;
vector3df m_vCurPos ;
f32 m_fSpeed ;
u32 m_uCurTime; //internal usage
vector3df m_vDir ;
vector3df m_vRDir ;
};
讀者能夠看到博主上面定義的類很的簡單,僅僅是加入了幾個成員屬性而已。除此之外就是復(fù)寫的方法和一個構(gòu)造函數(shù)。
為了實現(xiàn)前面描寫敘述的運動,我們來介紹下這里面成員的含義。首先,我們須要知道物體運動應(yīng)該從哪里開始。到哪里結(jié)束,而且須要知道這個運動的速度以及當(dāng)前運動的時間。另外兩個成員屬性用于內(nèi)部計算使用。以下來看看這個類的明白實現(xiàn)是怎么樣的:
#include"CMyRollAnimator.h" //constructor
CMyRollAnimator::CMyRollAnimator(vector3df _startPos, vector3df _endPos,
f32 _speed)
:m_vStartPos(_startPos),
m_vEndPos(_endPos),
m_fSpeed(_speed),
m_vCurPos(_startPos),
m_uCurTime(0)
{
m_vDir = m_vEndPos - m_vStartPos ;
m_vDir.normalize();
m_vRDir = m_vDir ;
m_vRDir.rotateXZBy(90.0f);
} //animate a node
void CMyRollAnimator::animateNode(ISceneNode* _pNode, u32 timeMs)
{
if(0 == m_uCurTime)
{
m_uCurTime = timeMs ;
_pNode->setPosition(m_vStartPos);
}
else
{
u32 _deltaTime = timeMs - m_uCurTime ;
f32 _fDeltaTime = _deltaTime / 1000.0f ;
m_uCurTime = timeMs ;
m_vCurPos += _fDeltaTime * m_fSpeed * m_vDir ; if(abs(m_vCurPos.X - m_vEndPos.X) < 0.01f &&
abs(m_vCurPos.Y - m_vEndPos.Y) < 0.01f &&
abs(m_vCurPos.Z - m_vEndPos.Z) < 0.01f)
{
m_vCurPos = m_vStartPos ;
} _pNode->setPosition(m_vCurPos);
_pNode->setRotation(-m_vRDir * 180.0f * timeMs/1000.0f);
}
}// end for animateNode //clone an animator
ISceneNodeAnimator* CMyRollAnimator::createClone(ISceneNode* node, ISceneManager* newManager)
{
CMyRollAnimator* _pAnimator = new CMyRollAnimator(m_vStartPos, m_vEndPos, m_fSpeed);
return _pAnimator ;
}// end for createClone
事實上實現(xiàn)。也十分的簡單。最重要的控制方案是在animateNode這個函數(shù)里面實現(xiàn)的。對于這個函數(shù),我們須要知道一點,它的參數(shù)列表中的第二個參數(shù)的意義并非類似cocos2d-x里面的調(diào)用該函數(shù)的時間間隔。在Irrlicht內(nèi)部維持了一個時間計數(shù)器,這個計數(shù)器從系統(tǒng)啟動開始就開始計時。這里的timeMs就是系統(tǒng)此時的時間,以MS計算。
所以讀者大概。就行明確博主為什么要在類內(nèi)部定義的當(dāng)前時間了。我們通過這個所謂的當(dāng)前時間來與系統(tǒng)實際的當(dāng)前時間進(jìn)行比較,計算出時間間隔,從而控制節(jié)點進(jìn)行移動。
想要實現(xiàn)自己的Animator是不是十分的簡單啊???僅僅要掌握了這個主要的規(guī)則。我們就能夠發(fā)揮我們自己的想象來制作出十分復(fù)雜的Animator出來。
大家能夠借鑒下cocos2d-x里面的Action,試著在Irrlicht中也實現(xiàn)它們,而且將代碼共享出來給大家使用。假設(shè)有空的話,博主可能會自己實現(xiàn)一些Animator共享給大家使用。
完整的代碼和教程4中的代碼一致。所不同的是將FlyCircleAnimator換成我們這里自定義的Animator而已,例如以下所看到的:
#include<irrlicht.h>
#include"MyEventReceiver.h"
#include"CMyRollAnimator.h"
using namespace irr;
using namespace core;
using namespace gui;
using namespace scene;
using namespace video; #ifdef _IRR_WINDOWS_
#pragma comment(lib,"irrlicht.lib")
#pragma comment(linker,"/subsystem:windows /ENTRY:mainCRTStartup")
#endif int main()
{
//Create the irrdevice
MyEventReceiver _receiver;
IrrlichtDevice* _pDevice = createDevice(EDT_DIRECT3D9,dimension2d<u32>(800,640),32U,
false,
false,
false,
&_receiver); //Check if create successfully
if(NULL == _pDevice)
return 1 ; //Get the video driver and scene manager
IVideoDriver* _pVideoDriver = _pDevice->getVideoDriver();
ISceneManager* _pSceneManager = _pDevice->getSceneManager(); //Create a sphere node
ISceneNode* _pNode = _pSceneManager->addSphereSceneNode(); //Check if create successfully
if(NULL == _pNode)
return 1 ;
_pNode->setPosition(vector3df(0,0,30));
_pNode->setMaterialTexture(0,_pVideoDriver->getTexture("wall.bmp"));
_pNode->setMaterialFlag(EMF_LIGHTING,false); //Create a cube node
ISceneNode* _pCubeNode = _pSceneManager->addCubeSceneNode(); //Check if create successfully
if(NULL == _pCubeNode)
return 1 ;
_pCubeNode->setMaterialTexture(0,_pVideoDriver->getTexture("t351sml.jpg"));
_pCubeNode->setMaterialFlag(EMF_LIGHTING, false); //Create a scene node animator for cube node
//ISceneNodeAnimator* _pAnimator = _pSceneManager->createFlyCircleAnimator(vector3df(0,0,30),
// 20.0f);
CMyRollAnimator * _pAnimator = new CMyRollAnimator(vector3df(-20,0,30), vector3df(20,0,30),
10.0f); //Check if create successfully
if(NULL == _pAnimator)
return 1 ; _pCubeNode->addAnimator(_pAnimator); //Drop the animator
_pAnimator->drop(); //Add one camera node
_pSceneManager->addCameraSceneNode(); int _nlastFPS = -1 ; u32 _uLastTime = _pDevice->getTimer()->getTime(); const f32 MOVEMENT_SPEED = 5.0f ; //Do loop
while(_pDevice->run())
{
const u32 _now = _pDevice->getTimer()->getTime();
const f32 _frameDeltaTime = (f32)(_now - _uLastTime)/1000.0f;
_uLastTime = _now ; vector3df _pos = _pNode->getPosition(); if(_receiver.IsKeyDown(KEY_KEY_W))
_pos.Y += MOVEMENT_SPEED * _frameDeltaTime ;
else if(_receiver.IsKeyDown(KEY_KEY_S))
_pos.Y -= MOVEMENT_SPEED * _frameDeltaTime ; if(_receiver.IsKeyDown(KEY_KEY_A))
_pos.X -= MOVEMENT_SPEED * _frameDeltaTime ;
else if(_receiver.IsKeyDown(KEY_KEY_D))
_pos.X += MOVEMENT_SPEED * _frameDeltaTime ; _pNode->setPosition(_pos); //Draw the scene
_pVideoDriver->beginScene();
_pSceneManager->drawAll();
_pVideoDriver->endScene();
}// end while _pDevice->drop(); return 0 ;
}// end
Referenced Counter
博主在編寫這個實驗程序的時候。發(fā)現(xiàn)當(dāng)我們調(diào)用_pAnimator->drop的時候,animator并沒有銷毀。所以,博主對于引擎內(nèi)部是怎樣保存一個對象技術(shù)感到好奇。決心研究一下。
在Irrlicht引擎中,通過一個十分簡單的引用技術(shù)(Referenced Counter)系統(tǒng)來對系統(tǒng)中的對象進(jìn)行跟蹤。
這個功能是通過一個名為IReferenceCounted接口來實現(xiàn)的,引擎中大部分的類都繼承了這個接口。從而實現(xiàn)了引用技術(shù)操作。以下來看下。這個類的申明:
// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h #ifndef __I_IREFERENCE_COUNTED_H_INCLUDED__
#define __I_IREFERENCE_COUNTED_H_INCLUDED__ #include "irrTypes.h" namespace irr
{ //! Base class of most objects of the Irrlicht Engine.
/** This class provides reference counting through the methods grab() and drop().
It also is able to store a debug string for every instance of an object.
Most objects of the Irrlicht
Engine are derived from IReferenceCounted, and so they are reference counted. When you create an object in the Irrlicht engine, calling a method
which starts with 'create', an object is created, and you get a pointer
to the new object. If you no longer need the object, you have
to call drop(). This will destroy the object, if grab() was not called
in another part of you program, because this part still needs the object.
Note, that you only need to call drop() to the object, if you created it,
and the method had a 'create' in it. A simple example: If you want to create a texture, you may want to call an imaginable method
IDriver::createTexture. You call
ITexture* texture = driver->createTexture(dimension2d<u32>(128, 128));
If you no longer need the texture, call texture->drop(). If you want to load a texture, you may want to call imaginable method
IDriver::loadTexture. You do this like
ITexture* texture = driver->loadTexture("example.jpg");
You will not have to drop the pointer to the loaded texture, because
the name of the method does not start with 'create'. The texture
is stored somewhere by the driver.
*/
class IReferenceCounted
{
public: //! Constructor.
IReferenceCounted()
: DebugName(0), ReferenceCounter(1)
{
} //! Destructor.
virtual ~IReferenceCounted()
{
} //! Grabs the object. Increments the reference counter by one.
/** Someone who calls grab() to an object, should later also
call drop() to it. If an object never gets as much drop() as
grab() calls, it will never be destroyed. The
IReferenceCounted class provides a basic reference counting
mechanism with its methods grab() and drop(). Most objects of
the Irrlicht Engine are derived from IReferenceCounted, and so
they are reference counted. When you create an object in the Irrlicht engine, calling a
method which starts with 'create', an object is created, and
you get a pointer to the new object. If you no longer need the
object, you have to call drop(). This will destroy the object,
if grab() was not called in another part of you program,
because this part still needs the object. Note, that you only
need to call drop() to the object, if you created it, and the
method had a 'create' in it. A simple example: If you want to create a texture, you may want to call an
imaginable method IDriver::createTexture. You call
ITexture* texture = driver->createTexture(dimension2d<u32>(128, 128));
If you no longer need the texture, call texture->drop().
If you want to load a texture, you may want to call imaginable
method IDriver::loadTexture. You do this like
ITexture* texture = driver->loadTexture("example.jpg");
You will not have to drop the pointer to the loaded texture,
because the name of the method does not start with 'create'.
The texture is stored somewhere by the driver. */
void grab() const { ++ReferenceCounter; } //! Drops the object. Decrements the reference counter by one.
/** The IReferenceCounted class provides a basic reference
counting mechanism with its methods grab() and drop(). Most
objects of the Irrlicht Engine are derived from
IReferenceCounted, and so they are reference counted. When you create an object in the Irrlicht engine, calling a
method which starts with 'create', an object is created, and
you get a pointer to the new object. If you no longer need the
object, you have to call drop(). This will destroy the object,
if grab() was not called in another part of you program,
because this part still needs the object. Note, that you only
need to call drop() to the object, if you created it, and the
method had a 'create' in it. A simple example: If you want to create a texture, you may want to call an
imaginable method IDriver::createTexture. You call
ITexture* texture = driver->createTexture(dimension2d<u32>(128, 128));
If you no longer need the texture, call texture->drop().
If you want to load a texture, you may want to call imaginable
method IDriver::loadTexture. You do this like
ITexture* texture = driver->loadTexture("example.jpg");
You will not have to drop the pointer to the loaded texture,
because the name of the method does not start with 'create'.
The texture is stored somewhere by the driver.
\return True, if the object was deleted. */
bool drop() const
{
// someone is doing bad reference counting.
_IRR_DEBUG_BREAK_IF(ReferenceCounter <= 0) --ReferenceCounter;
if (!ReferenceCounter)
{
delete this;
return true;
} return false;
} //! Get the reference count.
/** \return Current value of the reference counter. */
s32 getReferenceCount() const
{
return ReferenceCounter;
} //! Returns the debug name of the object.
/** The Debugname may only be set and changed by the object
itself. This method should only be used in Debug mode.
\return Returns a string, previously set by setDebugName(); */
const c8* getDebugName() const
{
return DebugName;
} protected: //! Sets the debug name of the object.
/** The Debugname may only be set and changed by the object
itself. This method should only be used in Debug mode.
\param newName: New debug name to set. */
void setDebugName(const c8* newName)
{
DebugName = newName;
} private: //! The debug name.
const c8* DebugName; //! The reference counter. Mutable to do reference counting on const objects.
mutable s32 ReferenceCounter;
}; } // end namespace irr #endif
這個接口十分的簡潔,我們僅僅要通過grab()和drop()這兩個函數(shù)來進(jìn)行操作就行。操作的規(guī)則在上面已經(jīng)說的很清楚了。
對于在引擎中使用create方法創(chuàng)造出來的對象,一般來說在create方面里面會調(diào)用new來構(gòu)造一個對象。看這個接口的構(gòu)造函數(shù)。我們發(fā)現(xiàn),進(jìn)行構(gòu)造之后引用計數(shù)就是1了。
假設(shè)我們接著調(diào)用drop()函數(shù)。引用計數(shù)值就會變成0,從而自我進(jìn)行銷毀。也就是說,調(diào)用drop的次數(shù)要比調(diào)用grab的次數(shù)大1才可以使引用計數(shù)值變?yōu)?,從而銷毀自身。
這就是導(dǎo)致在上面,博主調(diào)試實驗程序的時候,發(fā)現(xiàn)調(diào)用_pAnimator->drop()的時候,并沒有自我銷毀的原因。
在我們將animator加入到node里面的時候,node中會保存一個這個對象的副本,從而調(diào)用一次grab()函數(shù)。
也就是。我們須要再次的調(diào)用一次drop函數(shù),才可以銷毀掉這個animator。可是,博主這里并沒有這么做。
親愛的讀者們啊,也請你千萬不要這么做。
我們上面的全部創(chuàng)造過程,都是在程序主循環(huán)之上進(jìn)行構(gòu)造的。也就是說,在程序的主循環(huán)中還須要使用我們創(chuàng)建的這個animator,從而才可以隨著時間的進(jìn)行,控制節(jié)點進(jìn)行移動。
那么,就有一個問題了,這個對象究竟在什么地方進(jìn)行終于的銷毀了?
博主發(fā)現(xiàn),在教程4的結(jié)尾處,當(dāng)程序主循環(huán)結(jié)束之后,會調(diào)用一下_pDevice->drop()函數(shù)。是不是和這個對象的釋放有關(guān)了?
我們跟蹤這個類的析構(gòu)方法。依次得到例如以下的代碼:
CIrrDeviceStub::~CIrrDeviceStub()
{
VideoModeList->drop();
FileSystem->drop(); if (GUIEnvironment)
GUIEnvironment->drop(); if (VideoDriver)
VideoDriver->drop(); if (SceneManager)
SceneManager->drop(); if (InputReceivingSceneManager)
InputReceivingSceneManager->drop(); if (CursorControl)
CursorControl->drop(); if (Operator)
Operator->drop(); if (Randomizer)
Randomizer->drop(); CursorControl = 0; if (Timer)
Timer->drop(); if (Logger->drop())
os::Printer::Logger = 0;
}
CIrrDeviceStub是Irrlicht引擎中全部IrrlichtDevice設(shè)備的共同擁有的一個樁類。
用于實現(xiàn)那些全部設(shè)備都同樣的部分。通過這個樁類在IrrlichtDevice這個接口與實際的CIrrDeviceWin32之間進(jìn)行一個橋接。實現(xiàn)那些共同擁有的功能。在這個樁類析構(gòu)函數(shù)中,發(fā)現(xiàn)會依次的調(diào)用各個子系統(tǒng)的drop函數(shù),從而釋放里面的資源。對于node來說,它保存在SceneManager里面,繼續(xù)跟蹤,發(fā)如今引擎中僅僅有一個SceneManager。為CSceneManager。查看這個類的析構(gòu)函數(shù)。得到例如以下的函數(shù):
//! destructor
CSceneManager::~CSceneManager()
{
clearDeletionList(); //! force to remove hardwareTextures from the driver
//! because Scenes may hold internally data bounded to sceneNodes
//! which may be destroyed twice
if (Driver)
Driver->removeAllHardwareBuffers(); if (FileSystem)
FileSystem->drop(); if (CursorControl)
CursorControl->drop(); if (CollisionManager)
CollisionManager->drop(); if (GeometryCreator)
GeometryCreator->drop(); if (GUIEnvironment)
GUIEnvironment->drop(); u32 i;
for (i=0; i<MeshLoaderList.size(); ++i)
MeshLoaderList[i]->drop(); for (i=0; i<SceneLoaderList.size(); ++i)
SceneLoaderList[i]->drop(); if (ActiveCamera)
ActiveCamera->drop();
ActiveCamera = 0; if (MeshCache)
MeshCache->drop(); for (i=0; i<SceneNodeFactoryList.size(); ++i)
SceneNodeFactoryList[i]->drop(); for (i=0; i<SceneNodeAnimatorFactoryList.size(); ++i)
SceneNodeAnimatorFactoryList[i]->drop(); if (LightManager)
LightManager->drop(); // remove all nodes and animators before dropping the driver
// as render targets may be destroyed twice removeAll();
removeAnimators(); if (Driver)
Driver->drop();
}
在這個析構(gòu)函數(shù)中,能夠發(fā)現(xiàn)有一個名為removeAnimators()的函數(shù),我們繼續(xù)跟蹤,例如以下所看到的:
//! Removes all animators from this scene node.
/** The animators might also be deleted if no other grab exists
for them. */
virtual void removeAnimators()
{
ISceneNodeAnimatorList::Iterator it = Animators.begin();
for (; it != Animators.end(); ++it)
(*it)->drop(); Animators.clear();
}
哈哈,在這里,調(diào)用了一次drop(),從而實現(xiàn)了對Animator對象的銷毀。
在這次的追蹤發(fā)現(xiàn)了。當(dāng)我們調(diào)用_pDevice->drop函數(shù)的時候。會依次的釋放系統(tǒng)中全部的資源和對象。
可是這里,博主又有一個疑問?非常多對象僅僅有在這最后的步驟才會調(diào)用實際的析構(gòu)函數(shù)進(jìn)行銷毀。那么也就是說,假設(shè)在系統(tǒng)的中途。我們已經(jīng)決定了不再使用某個對象。而且保證在之后的過程中都不再使用了,假設(shè)僅僅是簡單的調(diào)用一次drop是不是還是會讓這個對象依舊殘留在系統(tǒng)里面,從而導(dǎo)致內(nèi)存被占用。不須要的資源長期持有著了?Irrlicht引擎是否有某種機制可以讓我們主動的釋放掉一些資源了?查看源碼發(fā)現(xiàn),的確有這種一些方法。大部分都是通過remove開頭的來實現(xiàn)這種功能。可是請注意,千萬不要主動的調(diào)用兩次drop()函數(shù)。
盡管這種確可以將對象提前銷毀,可是違背了使用引言計數(shù)的初衷。在Irrlicht內(nèi)部會出現(xiàn)野指針的情況。
畢竟你還是須要和Irrlicht打交道的,這樣做實在不好也不合理。
最適當(dāng)?shù)姆椒ā>褪峭ㄟ^remove方法來通知Irrlicht來釋放掉你不想使用的資源。
如今給大家總結(jié)下,本篇文章的核心內(nèi)容:
1.通過繼承ISceneNodeAnimator來實現(xiàn)我們自己的Animator
2.Irrlicht引用計數(shù)系統(tǒng)十分的簡單,卻很的有用。請大家依照它定義的規(guī)則來使用它。否則會出現(xiàn)意想不到的結(jié)果。
在博主自己進(jìn)行分析Irrlicht所使用的各種框架技術(shù)之后,博主有股沖動,想要自己的來實現(xiàn)下或者說仿制下這些技術(shù)。畢竟。研究引擎。一方面是為了熟悉它,使用它。另外一個更重要的原因是為了學(xué)習(xí)這些技術(shù),并把它據(jù)為己有。
所以。在今后,博主可能會另外在開一個系列。專門用來仿制引擎中的各種特性。試著實現(xiàn)一些仿真程序。
最后給出。本次試驗程序的程序截圖。十分的簡單,一個Cube來回的移動而已:
這次的文章就到此結(jié)束了,感謝大家的關(guān)注。!
!
總結(jié)
以上是生活随笔為你收集整理的Irrlicht 3D Engine 笔记系列 之 自己定义Animator的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ecshop 网站标题不更新或内容不更新
- 下一篇: NIO:异步非阻塞I/O,AIO,BIO