XNA Game Engine教程系列#6-Input
本教程我们将为我们的引擎建立一个输入系统。这一系统将支持Xbox 360游戏手柄、键盘和鼠标,并能扩展到支持大多数其他类型的设备(假设您知道如何与设备交互,XNA不直接支持其他设备)。
结构如下:
InputDevice:InputDevice类是输入设备的基类。输入设备是游戏引擎与设备之间的接口。它可以跟踪对象的状态并为获得设备数据提供功能。
我们要创建的输入设备是MouseDevice,KeyboardDevice和GamePadDevice。
但首先我要讨论什么是“泛型”。泛型能告诉C#我们的类或函数可以处理多种Type。Type是包含类信息的一个类。你可以使用MyObject.GetType()得到一个对象的类型,使用typeof(ClassName)获得一个类的类型。使用泛型的一个例子是XNA的”Content.Load ()”。因为它可以载入多种数据类型,它使用泛型告告知返回何种类型。这就是为什么我们可以载入纹理,模型等等(即:“Texture2D texture=Content.Load(“Content/TextureName ”))。在类名中我们告诉C#使用何种类型的泛型定义。当然,你可以使用任何一个或多个类型。泛型可以象一个普通类型被使用。下面是一个例子:
public class ExampleClass <T, R> { public T ReturnVariableT() { T object = new T(); return T; } public R ReturnVariableR() { R object = new R(); return T; } }
以下是InputDevice类的代码。这不是很复杂,基类的作用是保持一个状态对象。
// The base input device class. Other input device types will // inherit from it. The <T> generic allows us to specify what // type of input device state it manages. (MouseState, etc.) public abstract class InputDevice <T> : Component { // The State object of type T specified above public abstract T State { get; } }
我们还需要建立两个类:一个事件参数类和事件处理类,我们将使用这两个类与输入发生联系(如按下、释放按钮等)。在这之前,我想简单讨论一下事件。我们可以定义事件,其他类可以“hook”此事件,当事件触发时调用指定的方法。我们创建这样一个事件:
public event EventHandler ExampleEvent;
声明中的“EventHandler”部分告诉C#哪个方法被钩在事件上。EventHandler只是一个委托,它接受几个参数。在这里委托只是我们定义方法类型的一种方式。例如,指定EventHandler事件处理程序的一个事件将由以下格式的方法挂钩:
void FunctionToCallWhenEventIsFired(object sender, EventArgs e) { }
然后注册事件:
EventOwner.ExampleEvent += new EventHandler(FunctionToCallWhenEventIsFired);
现在,如果EventOwner引发了ExampleEvent,FunctionToCallWhenEventIsFired将被调用并传递叫做“object sender”对象和包含在“EventArgs e”中的事件的相关数据。我们可以从EventArgs继承来创建自己的类,并将自己的数据传递到事件处理程序。然后我们需要建立我们自己的EventHandler委托接受自定义事件参数类。
下面是自定义的事件参数类和事件处理类:
// An input device event argument class that can handle events // for several types of input device. "O" specified what type // of object the event was triggered by (Key, Button, // MouseButton, etc). "S" specifies the type of state the // event provides (MouseState, KeyboardState, etc.) public class InputDeviceEventArgs<O, S> : EventArgs { // The object of type O that triggered the event public O Object; // The input device that manages the state of type S that // owns the triggered object public InputDevice<S> Device; // The state of the input device of type S that was triggered public S State; // Constructor takes the triggered object and input device public InputDeviceEventArgs(O Object, InputDevice<S> Device) { this.Object = Object; this.Device = Device; this.State = ((InputDevice<S>)Device).State; } } // An input device event handler delegate. This defines what type // of method may handle an event. In this case, it is a void that // accepts the specified input device arguments public delegate void InputEventHandler<O, S>(object sender, InputDeviceEventArgs<O, S> e);
下面可以开始KeyboardDevice。下面是类定义,你可以看到我们指定KeyboardDevice跟踪KeyboardState对象。
using Microsoft.Xna.Framework.Input; using System; namespace Innovation { // An input device that manages the Keyboard and KeyboardState public class KeyboardDevice : InputDevice<KeyboardState> { } }
现在我们要添加一些成员,属性和事件。
// The last and current KeyboardStates KeyboardState last; KeyboardState current; // The keys that were pressed in the current state Keys[] currentKeys; // Public properties for the above members public override KeyboardState State { get { return current; } } public Keys[] PressedKeys { get { return currentKeys; } } // Events for when a key is pressed, released, and held. This // event can be handled by our InputEventHandler class. public event InputEventHandler<Keys, KeyboardState> KeyPressed; public event InputEventHandler<Keys, KeyboardState> KeyReleased; public event InputEventHandler<Keys, KeyboardState> KeyHeld;
接下去是构造函数:
// Constructor sets up the current state and updates for the // first time public KeyboardDevice() { current = Keyboard.GetState(); Update(); }
Update()方法。首先我们更新键盘状态:
public override void Update() { // Set the last state to the current one and update the // current state last = current; current = Keyboard.GetState(); // Set the currently pressed keys to the keys defined in // the current state currentKeys = current.GetPressedKeys(); }
现在,我们将检查需要触发的事件,并在需要时触发。我们要编写函数区检查不同的情况(按钮按下,释放等等)。
首先,我们需要建立一个类使我们能够列举一个枚举的值。在Windows平台我们可以使用Enum.GetValues()做到这一点,但Xbox和Zune的.Net Compact框架不支持,所以我们需要自己建立。添加一个新的叫做Util的静态类,并添加以下代码:
using System; using System.Collections.Generic; using System.Reflection; namespace Innovation { public static class Util { public static List<T> GetEnumValues<T>() { Type currentEnum = typeof(T); List<T> resultSet = new List<T>(); if (currentEnum.IsEnum) { FieldInfo[] fields = currentEnum.GetFields(BindingFlags.Static | BindingFlags.Public); foreach (FieldInfo field in fields) resultSet.Add((T)field.GetValue(null)); } else throw new ArgumentException("The argument must of type Enum or of a type derived from Enum", "T"); return resultSet; } } }
现在,我们可以遍历枚举的值了:
Enum ExampleEnum { One, Two, Three }; foreach(ExampleEnum value in Util.GetEnumValues<ExampleEnum>() { }
// For every key on the keyboard... foreach (Keys key in Util.GetEnumValues<Keys>()) { // If it is a new key press (this is the first frame // it is down), trigger the corresponding event if (WasKeyPressed(key)) if (KeyPressed != null) KeyPressed(this, new InputDeviceEventArgs <Keys, KeyboardState>(key, this)); // If it was just released (this is the first frame // it is up), trigger the corresponding event if (WasKeyReleased(key)) if (KeyReleased != null) KeyReleased(this, new InputDeviceEventArgs <Keys, KeyboardState>(key, this)); // If it has been held (it has been down for more // than one frame), trigger the corresponding event if (WasKeyHeld(key)) if (KeyHeld != null) KeyHeld(this, new InputDeviceEventArgs <Keys, KeyboardState>(key, this)); }
现在我们编写函数检查不同的状态(按钮上,下,按下,释放):
// Whether the specified key is currently down public bool IsKeyDown(Keys Key) { return current.IsKeyDown(Key); } // Whether the specified key is currently up public bool IsKeyUp(Keys Key) { return current.IsKeyUp(Key); } // Whether the specified key is down for the first time // this frame public bool WasKeyPressed(Keys Key) { if (last.IsKeyUp(Key) && current.IsKeyDown(Key)) return true; return false; } // Whether the specified key is up for the first time // this frame public bool WasKeyReleased(Keys Key) { if (last.IsKeyDown(Key) && current.IsKeyUp(Key)) return true; return false; } // Whether the specified key has been down for more than // one frame public bool WasKeyHeld(Keys Key) { if (last.IsKeyDown(Key) && current.IsKeyDown(Key)) return true; return false; }
MouseDevice和GamepadDevice工作原理类似,详见源代码。
就是这么多了!举例来说,如果你想监测键盘和鼠标,您可以在游戏中创建KeyboardDevice和MouseDevice的实例:
MouseDevice mouse = new MouseDevice(); KeyboardDevice keyboard = new KeyboardDevice();
然后,将它们添加到services中以监测它们:
Engine.Services.AddService(typeof(MouseDevice), mouse); Engine.Services.AddService(typeof(KeyboardDevice), keyboard);
例如,如果要在鼠标按钮按下后将盒撞击到其他盒上,可以在update方法中对代码做如下修改:
if (Engine.Services.GetService<MouseDevice>().WasButtonPressed(MouseButtons.Left)) { PhysicsActor act = new PhysicsActor( Engine.Content.Load<Model>("Content/ig_box"), new BoxObject(new Vector3(1), new Vector3(0, .5f, 1), Vector3.Zero)); act.Scale = new Vector3(1); act.PhysicsObject.Mass = 1000; act.PhysicsObject.Velocity = new Vector3(0, 2, -6); }
现在,我们有了一个完整的输入系统。也可以创建其他设备监控其他设备类型,如吉他,Zune的输入面板,或XNA还不支持的输入设备。
发布时间:2008/12/31 上午11:06:52 阅读次数:6804