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

来源:互联网 发布:数据库课程设计实验 编辑:程序博客网 时间:2024/04/18 10:53

在看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_PLATFORMim = new SDLInputManager();#elif defined OIS_WIN32_PLATFORMim = new Win32InputManager();#elif defined OIS_XBOX_PLATFORMim = new XBoxInputManager();#elif defined OIS_LINUX_PLATFORMim = new LinuxInputManager();#elif defined OIS_APPLE_PLATFORMim = new MacInputManager();#elseOIS_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 InstanceParamList::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 devicehr = 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 deviceobj->_initialize();}catch(...){//Somekind of error, cleanup and rethrowdestroyInputObject(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 valuesmState.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 ignoreif( FAILED(hr) )return;}bool axesMoved = false;//Accumulate all axis movements for one axesMove message..//Buttons are fired off as they are foundfor(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 forif( axesMoved ){if( coopSetting & DISCL_NONEXCLUSIVE ){//DirectInput provides us with meaningless values, so correct thatPOINT 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 windowif( 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 moveif( mListener && mBuffered )mListener->mouseMoved( MouseEvent( this, mState ) );}}

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

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

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

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


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

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

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


转载请注明