Irrlicht 3D Engine 筆記系列 之 本身定義Animator

做者: i_dovelemonnode

日期: 2014 / 12 / 17windows

來源: CSDN框架

主題: Custom Animator, Referenced countdom



引言

              在昨天的文章《Irrlicht 3D Engine 筆記系列 之 教程4 - Movement》中,博主向你們保證會在今天向你們實際操做怎樣擴展Irrlicht引擎的Animator特性。假設讀者對Irrlicht的Animator的特性不是很是瞭解的話,請先了解下前面的那篇文章,本片文章是在上次文章的基礎上進行的。ide



Custom Animator

              通過昨天咱們的分析,發現僅僅要咱們繼承ISceneNodeAnimator這個接口來實現咱們本身的Animator就可以了。因此如下,咱們就按着這個思路來對建立咱們本身定義的Animator。函數

            首先,對咱們將要建立的Animator進行下簡單的描寫敘述:oop

            博主想要建立一個沿着某條直線進行循環運動,並且物體自己在旋轉的Animator。博主稱之爲Roll Animator(翻滾動畫)。post

            好了。在明白了咱們想要建立的Animator的功能以後。咱們就實際動手來建立一個。學習

            首先,咱們來申明一個CMyRollAnimator的類,這個類繼承至ISceneNodeAnimator,並且複寫當中的兩個純虛擬函數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	;
};
          讀者可以看到博主上面定義的類很的簡單,僅僅是加入了幾個成員屬性而已。除此以外就是複寫的方法和一個構造函數。

           爲了實現前面描寫敘述的運動,咱們來介紹下這裏面成員的含義。首先,咱們需要知道物體運動應該從哪裏開始。到哪裏結束,並且需要知道這個運動的速度以及當前運動的時間。另外兩個成員屬性用於內部計算使用。如下來看看這個類的明白實現是怎麼樣的:

#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

         事實上實現。也十分的簡單。最重要的控制方案是在animateNode這個函數裏面實現的。對於這個函數,咱們需要知道一點,它的參數列表中的第二個參數的意義並不是相似cocos2d-x裏面的調用該函數的時間間隔。在Irrlicht內部維持了一個時間計數器,這個計數器從系統啓動開始就開始計時。這裏的timeMs就是系統此時的時間,以MS計算。

因此讀者大概。就行明確博主爲何要在類內部定義的當前時間了。咱們經過這個所謂的當前時間來與系統實際的當前時間進行比較,計算出時間間隔,從而控制節點進行移動。

        想要實現本身的Animator是否是十分的簡單啊???僅僅要掌握了這個主要的規則。咱們就可以發揮咱們本身的想象來製做出十分複雜的Animator出來。

你們可以借鑑下cocos2d-x裏面的Action,試着在Irrlicht中也實現它們,並且將代碼共享出來給你們使用。假設有空的話,博主可能會本身實現一些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

             博主在編寫這個實驗程序的時候。發現當咱們調用_pAnimator->drop的時候,animator並無銷燬。因此,博主對於引擎內部是怎樣保存一個對象技術感到好奇。決心研究一下。

           在Irrlicht引擎中,經過一個十分簡單的引用技術(Referenced Counter)系統來對系統中的對象進行跟蹤。

這個功能是經過一個名爲IReferenceCounted接口來實現的,引擎中大部分的類都繼承了這個接口。從而實現了引用技術操做。如下來看下。這個類的申明:

// 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()這兩個函數來進行操做就行。操做的規則在上面已經說的很清楚了。

對於在引擎中使用create方法創造出來的對象,通常來講在create方面裏面會調用new來構造一個對象。看這個接口的構造函數。咱們發現,進行構造以後引用計數就是1了。

假設咱們接着調用drop()函數。引用計數值就會變成0,從而自我進行銷燬。也就是說,調用drop的次數要比調用grab的次數大1纔可使引用計數值變爲0,從而銷燬自身。

         這就是致使在上面,博主調試實驗程序的時候,發現調用_pAnimator->drop()的時候,並無自我銷燬的緣由。

在咱們將animator加入到node裏面的時候,node中會保存一個這個對象的副本,從而調用一次grab()函數。

也就是。咱們需要再次的調用一次drop函數,才能夠銷燬掉這個animator。但是,博主這裏並無這麼作。

親愛的讀者們啊,也請你千萬不要這麼作。

咱們上面的所有創造過程,都是在程序主循環之上進行構造的。也就是說,在程序的主循環中還需要使用咱們建立的這個animator,從而才能夠隨着時間的進行,控制節點進行移動。

         那麼,就有一個問題了,這個對象究竟在什麼地方進行終於的銷燬了?

         博主發現,在教程4的結尾處,當程序主循環結束以後,會調用一下_pDevice->drop()函數。是否是和這個對象的釋放有關了?

         咱們跟蹤這個類的析構方法。依次獲得例如如下的代碼:

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設備的共同擁有的一個樁類。

用於實現那些所有設備都一樣的部分。經過這個樁類在IrrlichtDevice這個接口與實際的CIrrDeviceWin32之間進行一個橋接。實現那些共同擁有的功能。在這個樁類析構函數中,發現會依次的調用各個子系統的drop函數,從而釋放裏面的資源。對於node來講,它保存在SceneManager裏面,繼續跟蹤,發現在引擎中僅僅有一個SceneManager。爲CSceneManager。查看這個類的析構函數。獲得例如如下的函數:

//! 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();
}

          在這個析構函數中,可以發現有一個名爲removeAnimators()的函數,咱們繼續跟蹤,例如如下所看到的:

		//! 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();
		}

         哈哈,在這裏,調用了一次drop(),從而實現了對Animator對象的銷燬。

         在此次的追蹤發現了。當咱們調用_pDevice->drop函數的時候。會依次的釋放系統中所有的資源和對象。

         但是這裏,博主又有一個疑問?很是多對象僅僅有在這最後的步驟纔會調用實際的析構函數進行銷燬。那麼也就是說,假設在系統的中途。咱們已經決定了再也不使用某個對象。並且保證在以後的過程當中都再也不使用了,假設僅僅是簡單的調用一次drop是否是仍是會讓這個對象依舊殘留在系統裏面,從而致使內存被佔用。不需要的資源長期持有着了?Irrlicht引擎是否有某種機制可讓咱們主動的釋放掉一些資源了?查看源碼發現,的確有這種一些方法。大部分都是經過remove開頭的來實現這種功能。但是請注意,千萬不要主動的調用兩次drop()函數。

儘管這種確能夠將對象提早銷燬,但是違背了使用引言計數的初衷。在Irrlicht內部會出現野指針的狀況。

畢竟你仍是需要和Irrlicht打交道的,這樣作實在很差也不合理。

最適當的方法。就是經過remove方法來通知Irrlicht來釋放掉你不想使用的資源。

          

            現在給你們總結下,本篇文章的核心內容:

          1.經過繼承ISceneNodeAnimator來實現咱們本身的Animator

          2.Irrlicht引用計數系統十分的簡單,卻很的有用。請你們依照它定義的規則來使用它。不然會出現意想不到的結果。

         

           在博主本身進行分析Irrlicht所使用的各類框架技術以後,博主有股衝動,想要本身的來實現下或者說仿製下這些技術。畢竟。研究引擎。一方面是爲了熟悉它,使用它。另一個更重要的緣由是爲了學習這些技術,並把它據爲己有。

因此。在從此,博主可能會另外在開一個系列。專門用來仿製引擎中的各類特性。試着實現一些仿真程序。

           最後給出。本次試驗程序的程序截圖。十分的簡單,一個Cube來回的移動而已:

                     

          此次的文章就到此結束了,感謝你們的關注。!

相關文章
相關標籤/搜索