?
Torque X提供了多层输入管理的方式。之所以采用这种多层的系统,是为了让你无论从底层,还是上层,都能够连接到输入。这里只给出一个关于输入系统底层的粗略概览。我们会把重心放在上层,因为上层应当是你最常用的。
?
最底层是输入设备层。这是一个底层系统,我们将它打包进整个系统,并且以输入事件的方式发送。这样做的一个主要原因是,我们可以以一种确定的方式记录并回放,从而方便调试。这同样能够屏蔽底层所有输入的细节。非常方便不是吗?这样一来你可以通过侦听输入事件来观察所有从系统而来的输入。
?
在上面一层是输入管理器。输入管理器侦听所有输入事件,并将它们路由到映射或者GUI系统中。一般来说,你不需要要和输入事件层和输入管理器层打交道。
?
输入映射是我们处理输入遇到的第一个上层系统。输入映射允许你将输入事件映射到指定的回调函数上。例如,下面一个例子展示了如何将键盘按键事件映射到放大或缩小上:
?
int keyboardId = InputManager.Instance.FindDevice("keyboard"); InputMap.Global.ProcessBind(keyboardId, (int)Microsoft.Xna.Framework.Input.Keys.Z, ZoomIn); InputMap.Global.ProcessBind(keyboardId, (int)Microsoft.Xna.Framework.Input.Keys.X, ZoomOut);?
?
下面演示如何将游戏手柄0的X和Y按钮事件映射到放大或缩小上:
?
int gamepadId = InputManager.Instance.FindDevice("gamepad0"); InputMap.Global.ProcessBind(gamepadId, (int)XGamePadDevice.GamePadObjects.GamePadObjects.X,ZoomIn); InputMap.Global.ProcessBind(gamepadId, (int)XGamePadDevice.GamePadObjects.GamePadObjects.Y,ZoomOut);?
?
上面的代码在全局输入映射中做了绑定。你当然可以创建自己的输入映射栈,然后在你需要的时候压栈或出栈。输入映射栈被保存在输入管理器中,所以如果你想要创建一个输入映射并将其压栈,你可以这样做:
?
InputMap map = new InputMap(); int keyboardId = InputManager.Instance.FindDevice("keyboard"); map.ProcessBind(keyboardId, (int)Microsoft.Xna.Framework.Input.Keys.Z, ZoomIn); map.ProcessBind(keyboardId, (int)Microsoft.Xna.Framework.Input.Keys.X, ZoomOut); int gamepadId = InputManager.Instance.FindDevice("gamepad0"); map.ProcessBind(gamepadId, (int)XGamePadDevice.GamePadObjects.GamePadObjects.X,ZoomIn); map.ProcessBind(gamepadId, (int)XGamePadDevice.GamePadObjects.GamePadObjects.Y,ZoomOut); // now push the map InputManager.Instance.PushInputMap(map);?
?
出栈(这样就删除了这个映射):
// pop the map InputManager.Instance.PopInputMap(map);
??
?
输入映射为映射输入事件和动作提供了一个非常出色的上层集合。那么如何将一个游戏对象和输入连接?一个方法是:在这个游戏对象之上创建一大堆的诸如MoveX和MoveY的方法,通过这些方法连接到输入。例如:
?
class MyObj { void MoveX(float val) { _move.x = val; } void MoveY(float val) { _move.y = val; } void UpdateAnimation(float dt) { _velocity += _move * MoveScale; _velocity.X = MathHelper.Clamp(_velocity.X,-MaxVel,MaxVel); _velocity.Y = MathHelper.Clamp(_velocity.Y,-MaxVel,MaxVel); } }?
?
这显然不是一个好方法,因为你不得不在输入发生时为每个对象保存它们的动作。不仅仅是这样,如果一个游戏对象想以同样的操作方式(例如WASD代表前进后退左移右移)回应键盘和手柄输入,我们该怎么办?如果你想键盘上的按键能够缓慢地移动游戏对象,而手柄上的摇杆能够快速移动对象,该怎么办?显然这样管理每一个游戏对象会变得非常冗长。
?
这就是我们为什么需要动作管理器。你可以创建一个输入映射,将动作管理器和你的游戏对象关联起来。然后,你可以配置动作管理器,使之拥有多种类型的动作,例如针对按键的缓慢移动和针对摇杆的快速移动。
下面是例子:
?
// hook up game pad thumbsticks int gamepadId = InputManager.Instance.FindDevice("gamepad0"); inputMap.BindMove(gamepadId, (int)XGamePadDevice.GamePadObjects.LeftThumbX, MoveMapTypes.StickAnalogHorizontal, 0); inputMap.BindMove(gamepadId, (int)XGamePadDevice.GamePadObjects.LeftThumbY, MoveMapTypes.StickAnalogVertical, 0); inputMap.BindMove(gamepadId, (int)XGamePadDevice.GamePadObjects.RightThumbX, MoveMapTypes.StickAnalogHorizontal, 1); inputMap.BindMove(gamepadId, (int)XGamePadDevice.GamePadObjects.RightThumbY, MoveMapTypes.StickAnalogVertical, 1); // hook up keyboard to act like sticks int keyboardId = InputManager.Instance.FindDevice("keyboard"); inputMap.BindMove(keyboardId, (int)Keys.D, MoveMapTypes.StickDigitalRight, 0); inputMap.BindMove(keyboardId, (int)Keys.A, MoveMapTypes.StickDigitalLeft, 0); inputMap.BindMove(keyboardId, (int)Keys.W, MoveMapTypes.StickDigitalUp, 0); inputMap.BindMove(keyboardId, (int)Keys.S, MoveMapTypes.StickDigitalDown, 0); inputMap.BindMove(keyboardId, (int)Keys.Right, MoveMapTypes.StickDigitalRight, 1); inputMap.BindMove(keyboardId, (int)Keys.Left, MoveMapTypes.StickDigitalLeft, 1); inputMap.BindMove(keyboardId, (int)Keys.Up, MoveMapTypes.StickDigitalUp, 1); inputMap.BindMove(keyboardId, (int)Keys.Down, MoveMapTypes.StickDigitalDown, 1);?
?
第一块代码将游戏手柄的摇杆和动作管理器绑定。第二块代码将键盘上的一些按键和动作管理器绑定。这意味着动作管理器将会自动根据是摇杆还是按键来生成动作,当然,无论是摇杆还是按键最终都会被表示为“操纵杆”。MoveMapType这个枚举变量控制着绑定类型。通常,我们仅仅需要在模拟绑定还是数字绑定间做出选择。例如摇杆就是一个模拟设备,所以我们使用模拟映射来绑定摇杆。按键是数字设备,所以我们使用的是数字映射。注意我们之前提到过,不管是键盘还是摇杆最终都被映射为“操纵杆”。这样做的目的是为设备提供一个统一的抽象表示,这里的操纵杆(抽象)和摇杆(实物)不是一回事。
?
动作管理器可以区别处理这两种输入。我们可以通过在模拟输入之上附加一个曲线函数来调节输入大小。下面的例子展示了如何配置动作管理器使之在水平方向上对输入做平方调整。
?
float [] scalevalues = { 0, 0.25f, 1.0f }; MoveManager.Instance.ConfigureStickHorizontalScaling(0,scalevalues);?
?
上面的函数会将(0,0),(0.5,0.25),(1,1)三个点传入,从而来调整输入大小。如果你想要提供更多点的话,比如4个点,那么这四个点的横坐标应该是0,0.33,0.66和1。(我总算看明白了,横坐标会根据你传入的数组长度来自动等分,你只需要指定对应横坐标的纵坐标就可以了)。
同样的,你可以控制按键需要多久来改变由动作管理器追踪的“操纵杆”:
?
float [] rampUpValue = null; float rampUpTime = 0.25f; float [] rampDownValue = {1, 0.25, 0 }; float rampDownTime = 0.5f; MoveManager.Instance.ConfigureStickHorizontalTracking(0, rampUpTime, rampUpValue, rampDownTime, rampDownValue);?
?
上面的代码告诉动作管理器需要花费1/4秒来移动“操纵杆”。没有任何曲率,所以将会以线性方式移动。将“操纵杆”移回中央位置需要1/2秒。既然我们传递了一个曲线函数,我们可以明显地发现在1/2秒的前1/4秒,“操纵杆”会移动0.75,而剩下的1/4秒仅仅移动0.25。
?
当你设定好了动作管理器,你就要准备接收这些动作了(好激动啊…!)。在你的游戏对象中新建一个拥有TickCallback方法的组件。例如,在太空打枪者(怎么又是打枪)这款游戏中,你可以在PlayerControlComponent._OnRegister方法中看到如下代码:
ProcessList.Instance.AddTickCallback(Owner, this);
??
?
PlayerControlComponent实现了ProcessTick方法来真正地处理输入:
?
public void ProcessTick(Move move, float elapsed) { // ... if (move != null && move.Buttons.Count > 0 && move.Sticks.Count > 0) { if (move.Buttons[1].Pushed) _switchWeapon(0); else if (move.Buttons[2].Pushed) _switchWeapon(1); else if (move.Buttons[3].Pushed) _switchWeapon(2); else if (move.Buttons[4].Pushed) _switchWeapon(3); if (move.Sticks[0].X != 0 || move.Sticks[0].Y != 0) { // handle left stick ... } if (move.Sticks[1].X != 0 || move.Sticks[1].Y != 0) { // handle right stick ... } ... }?
?
尽管这些代码看上去是在和“操纵杆”打交道,但实际上这里的“操纵杆”可以是键盘输入,或者是游戏手柄,甚至可以是方向盘。
?
将输入映射和动作管理器映射绑定并发送给你的游戏对象需要以下几个步骤:
?
注意:在下面这个例子中,player是指控制输入的玩家,而player object是指被玩家控制的对象。
?
// Set playerObject as the controllable object PlayerManager.Instance.GetPlayer(playerIndex).ControlObject = playerObject; // Get input map for this player and configure it InputMap inputMap = PlayerManager.Instance.GetPlayer(playerIndex).InputMap; // Get move manager for this player and configure it MoveManager moveManager = PlayerManager.Instance.GetPlayer(playerIndex).MoveManager;?
?
通过使用角色管理器你不需要了解输入映射、动作管理器和角色对象是如何交互的。只需要简单地在角色管理器中给角色对象设定输入映射就可以了。为角色对象设置一个控制对象可以让角色对象成为玩家动作的接受者。
角色管理器还提供了一个方便的关联玩家数据的方法,例如:
?
PlayerManager.Instance.GetPlayer(0).SetData("score",1000); String name; PlayerManager.Instance.GetPlayer(0).GetData("name", out name); PlayerManager.Player player = PlayerManager.Instance.GetPlayer(killedObject); if (player != null) { // our player was killed...decrement lives and respawn int lives; player.GetData("lives",out lives); player.SetData("lives",--lives); if (lives>0) RespawnPlayer(player); else GameOver(player); }?
?
?