第十五章 键盘控制
这一节我们学习最基本的键盘控制。下面是截图和代码,你可以使用 [I] 、[K] 、[J] 、[L] 控制角色的前后左右移动。
OGRE 使用 InputReader 类来接收系统输入信息,包括键盘输入和鼠标输入。 Ogre::InputReader 类在头文件 OgreInput.h 中定义,对于键盘控制,我们最常用的一个方法是 isKeyDown :
bool Ogre::InputReader:: isKeyDown ( KeyCode kc ) const [virtual]
当 KeyCode kc 指定的键被按下时返回真。
其中, KeyCode 的枚举也在头文件 OgreInput.h 中定义:
enum KeyCode { KC_ESCAPE =0x01, KC_1 =0x02, KC_2 =0x03, KC_3 =0x04, KC_4 =0x05, KC_5 =0x06, KC_6 =0x07, KC_7 =0x08, KC_8 =0x09, KC_9 =0x0A, KC_0 =0x0B, KC_MINUS =0x0C, /* - on main keyboard */ KC_EQUALS =0x0D, KC_BACK =0x0E, /* backspace */ KC_TAB =0x0F, KC_Q =0x10, KC_W =0x11, KC_E =0x12, KC_R =0x13, KC_T =0x14, KC_Y =0x15, KC_U =0x16, KC_I =0x17, KC_O =0x18, KC_P =0x19, KC_LBRACKET =0x1A, KC_RBRACKET =0x1B, KC_RETURN =0x1C, /* Enter on main keyboard */ KC_LCONTROL =0x1D, KC_A =0x1E, KC_S =0x1F, KC_D =0x20, KC_F =0x21, KC_G =0x22, KC_H =0x23, KC_J =0x24, KC_K =0x25, KC_L =0x26, KC_SEMICOLON =0x27, KC_APOSTROPHE =0x28, KC_GRAVE =0x29, /* accent grave */ KC_LSHIFT =0x2A, KC_BACKSLASH =0x2B, KC_Z =0x2C, KC_X =0x2D, KC_C =0x2E, KC_V =0x2F, KC_B =0x30, KC_N =0x31, KC_M =0x32, KC_COMMA =0x33, KC_PERIOD =0x34, /* . on main keyboard */ KC_SLASH =0x35, /* ''/'' on main keyboard */ KC_RSHIFT =0x36, KC_MULTIPLY =0x37, /* * on numeric keypad */ KC_LMENU =0x38, /* left Alt */ KC_SPACE =0x39, KC_CAPITAL =0x3A, KC_F1 =0x3B, KC_F2 =0x3C, KC_F3 =0x3D, KC_F4 =0x3E, KC_F5 =0x3F, KC_F6 =0x40, KC_F7 =0x41, KC_F8 =0x42, KC_F9 =0x43, KC_F10 =0x44, KC_NUMLOCK =0x45, KC_SCROLL =0x46, /* Scroll Lock */ KC_NUMPAD7 =0x47, KC_NUMPAD8 =0x48, KC_NUMPAD9 =0x49, KC_SUBTRACT =0x4A, /* - on numeric keypad */ KC_NUMPAD4 =0x4B, KC_NUMPAD5 =0x4C, KC_NUMPAD6 =0x4D, KC_ADD =0x4E, /* + on numeric keypad */ KC_NUMPAD1 =0x4F, KC_NUMPAD2 =0x50, KC_NUMPAD3 =0x51, KC_NUMPAD0 =0x52, KC_DECIMAL =0x53, /* . on numeric keypad */ KC_OEM_102 =0x56, /*
<
>
| on UK/Germany keyboards */ KC_F11 =0x57, KC_F12 =0x58, KC_F13 =0x64, /* (NEC PC98) */ KC_F14 =0x65, /* (NEC PC98) */ KC_F15 =0x66, /* (NEC PC98) */ KC_KANA =0x70, /* (Japanese keyboard) */ KC_ABNT_C1 =0x73, /* / ? on Portugese (Brazilian) keyboards */ KC_CONVERT =0x79, /* (Japanese keyboard) */ KC_NOCONVERT =0x7B, /* (Japanese keyboard) */ KC_YEN =0x7D, /* (Japanese keyboard) */ KC_ABNT_C2 =0x7E, /* Numpad . on Portugese (Brazilian) keyboards */ KC_NUMPADEQUALS =0x8D, /* = on numeric keypad (NEC PC98) */ KC_PREVTRACK =0x90, /* Previous Track (KC_CIRCUMFLEX on Japanese keyboard) */ KC_AT =0x91, /* (NEC PC98) */ KC_COLON =0x92, /* (NEC PC98) */ KC_UNDERLINE =0x93, /* (NEC PC98) */ KC_KANJI =0x94, /* (Japanese keyboard) */ KC_STOP =0x95, /* (NEC PC98) */ KC_AX =0x96, /* (Japan AX) */ KC_UNLABELED =0x97, /* (J3100) */ KC_NEXTTRACK =0x99, /* Next Track */ KC_NUMPADENTER =0x9C, /* Enter on numeric keypad */ KC_RCONTROL =0x9D, KC_MUTE =0xA0, /* Mute */ KC_CALCULATOR =0xA1, /* Calculator */ KC_PLAYPAUSE =0xA2, /* Play / Pause */ KC_MEDIASTOP =0xA4, /* Media Stop */ KC_VOLUMEDOWN =0xAE, /* Volume - */ KC_VOLUMEUP =0xB0, /* Volume + */ KC_WEBHOME =0xB2, /* Web home */ KC_NUMPADCOMMA =0xB3, /* , on numeric keypad (NEC PC98) */ KC_DIVIDE =0xB5, /* / on numeric keypad */ KC_SYSRQ =0xB7, KC_RMENU =0xB8, /* right Alt */ KC_PAUSE =0xC5, /* Pause */ KC_HOME =0xC7, /* Home on arrow keypad */ KC_UP =0xC8, /* UpArrow on arrow keypad */ KC_PGUP =0xC9, /* PgUp on arrow keypad */ KC_LEFT =0xCB, /* LeftArrow on arrow keypad */ KC_RIGHT =0xCD, /* RightArrow on arrow keypad */ KC_END =0xCF, /* End on arrow keypad */ KC_DOWN =0xD0, /* DownArrow on arrow keypad */ KC_PGDOWN =0xD1, /* PgDn on arrow keypad */ KC_INSERT =0xD2, /* Insert on arrow keypad */ KC_DELETE =0xD3, /* Delete on arrow keypad */ KC_LWIN =0xDB, /* Left Windows key */ KC_RWIN =0xDC, /* Right Windows key */ KC_APPS =0xDD, /* AppMenu key */ KC_POWER =0xDE, /* System Power */ KC_SLEEP =0xDF, /* System Sleep */ KC_WAKE =0xE3, /* System Wake */ KC_WEBSEARCH =0xE5, /* Web Search */ KC_WEBFAVORITES =0xE6, /* Web Favorites */ KC_WEBREFRESH =0xE7, /* Web Refresh */ KC_WEBSTOP =0xE8, /* Web Stop */ KC_WEBFORWARD =0xE9, /* Web Forward */ KC_WEBBACK =0xEA, /* Web Back */ KC_MYCOMPUTER =0xEB, /* My Computer */ KC_MAIL =0xEC, /* Mail */ KC_MEDIASELECT =0xED /* Media Select */ };
下面分析源代码。
我们需要控制的角色节点,在方法
void KeyboardApplication::createScene( void )
中建立:
// 创建模型实体 Entity* entObject = mSceneMgr->createEntity( "object", "ninja.mesh" ); entObject->setCastShadows( true ); SceneNode* objectNode = rootNode->createChildSceneNode( "objectNode" ); objectNode->attachObject( entObject ); objectNode->setPosition( Vector3( 0, -27, 0 ) );
注意其中这一句:
SceneNode* objectNode = rootNode->createChildSceneNode( "objectNode" );
我们在创建这个场景节点的时候,在括号里顺手为它起了一个名字叫作 "objectNode" ,这是有用处的,以后我们就可以根据这个名字,使用
SceneNode * Ogre::SceneManager:: getSceneNode ( const String & name ) const [virtual]
通过提供场景节点的名字来获取这个场景节点。
因为 SceneManager* mSceneMgr 是应用程序类 Ogre:: ExampleApplication 的保护成员,而我们要控制角色节点的移动,必须在帧监听器类 Ogre:: ExampleFrameListener 中逐帧控制,所以我们必须从 ExampleApplication 向 ExampleFrameListener 传递 mSceneMgr 实例参数。总体说来我们需要这样做:
(1) 在
void KeyboardApplication::createScene( void )
方法中,为角色实体建立并绑定场景到一个节点,并赋予这个场景节点一个唯一的名字;
(2) 在
void KeyboardApplication::createFrameListener( void )
方法中,新建帧监听器实例并向其传递 mSceneMgr :
// 新建帧监听器实例 mFrameListener= new KeyboardFrameListener( mWindow, mCamera, mSceneMgr ); mRoot->addFrameListener( mFrameListener ); // 添加帧监听器到根节点
(3) 重载 KeyboardFrameListener 的构造函数并接收上一步传递过来的 mSceneMgr 实例参数:
KeyboardFrameListener::KeyboardFrameListener( RenderWindow* win, Camera* cam, SceneManager* sm ) : ExampleFrameListener( win, cam ), mSceneMgr( sm ) { }
(4) 使用
SceneNode * Ogre::SceneManager:: getSceneNode ( const String & name ) const [virtual]
方法,使用角色场景节点的名字来获取这个场景节点:
mSceneNode = mSceneMgr->getSceneNode( "objectNode" ); // 获取角色对象的场景节点
然后嘛,我们就可以对这个场景节点为所欲为了。
我们要在每帧实时控制场景管理器或场景节点的属性,都可以使用这个通用方法。只要我们在帧监听器类中获取了场景节点的实例 mSceneMgr ,我们就可以使用 Ogre::SceneManager 中的大量 get 方法,例如:
virtual Camera * getCamera (const String &name) // 获取摄像机 virtual Light * getLight (const String &name) // 获取光源 virtual Material * getMaterial (const String &name) // 获取材质 virtual SceneNode * getRootSceneNode (void) const // 获取场景根节点 virtual SceneNode * getSceneNode (const String &name) const // 获取场景节点 virtual Entity * getEntity (const String &name) // 获取实体 virtual BillboardSet * getBillboardSet (const String &name) // 获取公告板集 virtual Animation * getAnimation (const String &name) const // 获取动画 virtual AnimationState * getAnimationState (const String &animName) // 获取动画状态 virtual Overlay * getOverlay (const String &name) // 获取盖层(界面)
来获取场景中的任意实例。条件就是你在创建这些实例的时候务必要顺手给它起个名字,方便以后按名字查找。
最后是 static Real timeDelay (时间延迟) 这个静态变量的作用,用来控制接收键盘输入的时间间隔。因为如果我们不去控制每隔多长时间移动一次的话,角色就每帧移动一次,这样,在帧数 FPS 很高的时候它就移动得很快,在 FPS 低的时候它就移动得很慢,我们无法预料客户机的 FPS ,所以如果不使用这个 timeDelay ,我们无法控制角色的实际移动速度。
时间延迟的每帧刷新使用下面的代码:
if (timeDelay >= 0) timeDelay -= evt.timeSinceLastFrame; // 时间延迟 -= 从上次调用时到现在所经历的时间
然后每接收一次键盘输入就使时间延迟重新倒数:
if (mInputDevice->isKeyDown( KC_I ) && timeDelay <= 0) { mSceneNode->translate( Vector3( 0, 0, -walkSpeed ) ); timeDelay = 0.02; }
timeSinceLastFrame 是 Ogre:: FrameEvent 的数据成员,在头文件 OgreFrameListener.h 中定义,单位是秒:
Real Ogre::FrameEvent::timeSinceLastFrame
这个数据成员记录的是:上一次访问 FrameEvent::timeSinceLastFrame 属性到现在的时间差(秒)。