关于Ogre自带输入系统OIS(Object-Oriented Input System)的源码分析

在看Ogre源码的时候注意到了这个开源的输入系统OIS,全称是Object-Oriented Input System。

好像我看到有人称为 Open Input System,不管叫什么总之还挺好用的。

OIS应该有其他平台的版本,但目前我只在Win32需要,所以其他平台的OIS版本不做讨论。


我使用的OIS版本是1.2.0, 现在应该已经有新的版本了,不过应该变化不会太大。

注意:

1. 我们此次的讨论和Ogre无任何关系,完全是直接对OIS的使用。

2. 关于OIS的手柄和力反馈部分不做讨论,有兴趣我之后会研究下。


OK! 让我们开始。。。


首先让我们看下OIS的整体目录结构



Extras部分不做讨论,貌似是支持了其他手柄,比如wii

前面说过OIS是区分平台的,所以可以看到目录结构中有个Win32的文件夹,下面放得就是Win32下使用的源代码。

而直接在Header Files下面的头文件和源文件均为平台通用的接口层,具体实现层就在类似Win32,Linux等文件夹下(我猜的,因为我没下载其他平台的版本)


下看下各个类之间的关系图



如上图, 可以看到所有Mouse,Keyboard,JoyStick都是继承自Object,具体平台的实现层其实是前面带Win32的,而接口正如前面说的是中间那3个类。



如上图,OIS的对输入的管理无疑是归InputManager管理了。


接下来看源代码,先从管理类InputManager开始

InputManager的类中有四个重要的函数,如下:

static InputManager* createInputSystem( ParamList &paramList );

static void destroyInputSystem(InputManager* manager);

Object* createInputObject( Type iType, bool bufferMode, const std::string &vendor = "");

void destroyInputObject( Object* obj );

前两个函数是用来创建和销毁OIS整个系统的,OIS在Win32上使用的是DX的DirectInput,具体DirectInput的使用方法在此不做讨论。

后两个函数是用来创建和销毁对象的(鼠标,键盘,手柄)。

InputManager.cpp
InputManager* InputManager::createInputSystem( ParamList ¶mList )
{
	InputManager* im = 0;

#if defined OIS_SDL_PLATFORM
	im = new SDLInputManager();
#elif defined OIS_WIN32_PLATFORM
	im = new Win32InputManager();
#elif defined OIS_XBOX_PLATFORM
	im = new XBoxInputManager();
#elif defined OIS_LINUX_PLATFORM
	im = new LinuxInputManager();
#elif defined OIS_APPLE_PLATFORM
	im = new MacInputManager();
#else
	OIS_EXCEPT(E_General, "No platform library.. check build platform defines!");
#endif 

	try
	{
		im->_initialize(paramList);
	}
	catch(...)
	{
		delete im;
		throw; //rethrow
	}

	return im;
}

看上面的代码可以发现针对不通的平台会有不通的InputManager创建,Win32下自然就是Win32InputManager了。

他调用了Win32InputManager下的初始化函数。

Win32InputManager.cpp
void Win32InputManager::_initialize( ParamList ¶mList )
{
	HINSTANCE hInst = 0;
	HRESULT hr;

	//TODO 64 bit proof this little conversion xxx wip
	//First of all, get the Windows Handle and Instance
	ParamList::iterator i = paramList.find("WINDOW");
	if( i == paramList.end() ) 
		OIS_EXCEPT( E_InvalidParam, "Win32InputManager::Win32InputManager >> No HWND found!" );

	hWnd  = (HWND)strtoul(i->second.c_str(), 0, 10);

	if( IsWindow(hWnd) == 0 )
		OIS_EXCEPT( E_General, "Win32InputManager::Win32InputManager >> The sent HWND is not valid!");

	hInst = GetModuleHandle(0);

	//Create the device
	hr = DirectInput8Create( hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&mDirectInput, NULL );
    if (FAILED(hr))	
		OIS_EXCEPT( E_General, "Win32InputManager::Win32InputManager >> Not able to init DirectX8 Input!");

	//Ok, now we have DirectInput, parse whatever extra settings were sent to us
	_parseConfigSettings( paramList );
	_enumerateDevices();
}
看代码可以发现,是对DirectInput8的创建,然后设置了参数,然后去枚举设备了。

InputManager.cpp
Object* InputManager::createInputObject( Type iType, bool bufferMode, const std::string &vendor )
{
	Object* obj = 0;
	FactoryList::iterator i = mFactories.begin(), e = mFactories.end();
	for( ; i != e; ++i)
	{
		if( (*i)->freeDevices(iType) > 0 )
		{
			if( vendor == "" || (*i)->vendorExist(iType, vendor) )
			{
				obj = (*i)->createObject(this, iType, bufferMode, vendor);
				mFactoryObjects[obj] = (*i);
				break;
			}
		}
	}

	if(!obj)
		OIS_EXCEPT(E_InputDeviceNonExistant, "No devices match requested type.");

	try
	{	//Intialize device
		obj->_initialize();
	}
	catch(...)
	{	//Somekind of error, cleanup and rethrow
		destroyInputObject(obj);
		throw;
	}

	return obj;
}
从上面的代码可以看到其实没做什么,只是调用了createObject去创建Object,然后就初始化_initialize去了。

这两个函数很自然的想到是和平台相关的,具体的实现自然要到平台相关的InputManager中要,

Win32InputManager.cpp
Object* Win32InputManager::createObject(InputManager* creator, Type iType, bool bufferMode, const std::string & vendor)
{
	Object *obj = 0;

	switch(iType)
	{
	case OISKeyboard: 
	{
		if( keyboardUsed == false )
			obj = new Win32Keyboard(this, mDirectInput, bufferMode, kbSettings);
		break;
	}
	case OISMouse:
	{
		if( mouseUsed == false )
			obj = new Win32Mouse(this, mDirectInput, bufferMode, mouseSettings);
		break;
	}
	case OISJoyStick:
	{
		for(JoyStickInfoList::iterator i = unusedJoyStickList.begin(); i != unusedJoyStickList.end(); ++i)
		{
			if(vendor == "" || i->vendor == vendor)
			{
				obj = new Win32JoyStick(this, mDirectInput, bufferMode, joySettings, *i);
				unusedJoyStickList.erase(i);
				break;
			}
		}
		break;
	}
	default:
		break;
	}

	if( obj == 0 )
		OIS_EXCEPT(E_InputDeviceNonExistant, "No devices match requested type.");

	return obj;
}
到这就可以看到具体创建不通的设备的代码了。


如上就完成了对OIS以及各个设备的创建,关于销毁的部分就非常简单了,就不讨论了。


下面看下具体设备的代码。

先看下各个设备的父类也就是Object类,他里面的东西都是各个设备的通用属性,比如类型,名字,是否是缓冲模式等,有些函数是接口,需要各个设备自己实现。

接下来看看Mouse类和Win32Mouse类,根据上面InputManager和Win32InputManager的理解,这两个类肯定也是一个是对外的接口类,一个是具体实现类。

Mouse类很简单,三个函数

virtual void setEventCallback( MouseListener *mouseListener ) {mListener = mouseListener;}
MouseListener* getEventCallback() {return mListener;}
const MouseState& getMouseState() const { return mState; }

设置对鼠标的监听类,获得监听类指针以及获得鼠标的状态。

WIn32Mouse类同样简单,四个主要函数:

virtual void setBuffered(bool buffered); // 设置缓冲或者非缓冲模式

virtual void capture(); // 捕获鼠标,需要反复地调用

virtual void _initialize(); // 对鼠标的创建,看代码可以看到DirectInput的创建函数

bool _doMouseClick( int mouseButton, DIDEVICEOBJECTDATA& di );  // 在缓冲模式下有效的函数,用来调用已经注册的监听者

Win32Mouse.cpp
void Win32Mouse::capture()
{
	//Clear old relative values
	mState.X.rel = mState.Y.rel = mState.Z.rel = 0;

	DIDEVICEOBJECTDATA diBuff[MOUSE_DX_BUFFERSIZE];
	DWORD entries = MOUSE_DX_BUFFERSIZE;

	HRESULT hr = mMouse->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), diBuff, &entries, 0 );
	if( hr != DI_OK )
	{
		hr = mMouse->Acquire();
		while( hr == DIERR_INPUTLOST ) 
			hr = mMouse->Acquire();

		hr = mMouse->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), diBuff, &entries, 0 );
		
		//Perhaps the user just tabbed away, and coop settings
		//are nonexclusive..so just ignore
		if( FAILED(hr) )
			return;
	}

	bool axesMoved = false;
	//Accumulate all axis movements for one axesMove message..
	//Buttons are fired off as they are found
	for(unsigned int i = 0; i < entries; ++i )
	{
		switch( diBuff[i].dwOfs )
		{
			case DIMOFS_BUTTON0:
				if(!_doMouseClick(0, diBuff[i])) return;
				break;
			case DIMOFS_BUTTON1:
				if(!_doMouseClick(1, diBuff[i])) return;
				break;
			case DIMOFS_BUTTON2:
				if(!_doMouseClick(2, diBuff[i])) return;
				break;
			case DIMOFS_BUTTON3:
				if(!_doMouseClick(3, diBuff[i])) return;
				break;
			case DIMOFS_BUTTON4:
				if(!_doMouseClick(4, diBuff[i])) return;
				break;	
			case DIMOFS_BUTTON5:
				if(!_doMouseClick(5, diBuff[i])) return;
				break;
			case DIMOFS_BUTTON6:
				if(!_doMouseClick(6, diBuff[i])) return;
				break;
			case DIMOFS_BUTTON7:
				if(!_doMouseClick(7, diBuff[i])) return;
				break;
			case DIMOFS_X:
				mState.X.rel += diBuff[i].dwData;
				axesMoved = true;
				break;
			case DIMOFS_Y:
				mState.Y.rel += diBuff[i].dwData;
				axesMoved = true;
				break;
			case DIMOFS_Z:
				mState.Z.rel += diBuff[i].dwData;
				axesMoved = true;
				break;
			default: break;
		} //end switch
	}//end for

	if( axesMoved )
	{
		if( coopSetting & DISCL_NONEXCLUSIVE )
		{
			//DirectInput provides us with meaningless values, so correct that
			POINT point;
			GetCursorPos(&point);
			ScreenToClient(mHwnd, &point);
			mState.X.abs = point.x;
			mState.Y.abs = point.y;
		}
		else
		{
			mState.X.abs +=  mState.X.rel;
			mState.Y.abs +=  mState.Y.rel;
		}
		mState.Z.abs +=  mState.Z.rel;

		//Clip values to window
		if( mState.X.abs < 0 )
			mState.X.abs = 0;
		else if( mState.X.abs > mState.width )
			mState.X.abs = mState.width;
		if( mState.Y.abs < 0 )
			mState.Y.abs = 0;
		else if( mState.Y.abs > mState.height )
			mState.Y.abs = mState.height;

		//Do the move
		if( mListener && mBuffered )
			mListener->mouseMoved( MouseEvent( this, mState ) );
	}
}

从上面的代码可以看到在获取完数据后会调用_doMouseClick函数来处理鼠标事件,

在往后可以看到在所有都处理完成后会判断如果是缓冲模式的话,调用mouseMoved将鼠标事件传递下去。

在_doMouseClick函数中也和mouseMoved类似会做缓冲和非缓冲的判断。

鼠标,键盘,手柄的处理有相似的地方,就不做讨论了。


关于OIS的使用,我想如果理解了OIS的具体实现,要使用起来肯定很容易,在此就暂时不做讨论了。

下面的链接是一个OIS的鼠标和键盘的示例代码链接。

http://download.csdn.net/detail/love0_0xin/4171517


转载请注明







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值