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

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号