【译】Microsoft Robotics Studio – 一个手柄服务
发表于2016-03-09
原文地址:http://www.codeproject.com/Articles/20393/Microsoft-Robotics-Studio-A-pad-service
版权:作者版权声明为http://www.codeproject.com/info/cpol10.aspx,可以翻译
一、简介
在这篇文章里面你将看到怎么用Microsoft Robotics来创建一个游戏手柄服务。这篇文章不是CCR或者DSS的教程,对于这些技术有了解是非常有用的,这篇文章将假设你知道什么是消息、队列以及服务。
基本的想法很简单:服务每隔x毫秒调用一个手柄服务。手柄服务调用rect input并且返回-1到1。比如左上角是(-1,1),右下角是(1,-1)。
这个过程中不需要计时器或者线程。我将使用消息、队列和CCR类。
二、背景
对于硬件(手柄)说几句。我使用的是Logitech Dual Action Gamepad。对于游戏来说是一个很好很便宜的设备,精度足够控制机器人。我没用过Direct Input所以我被迫在代码里面硬编码数字。我假设这些数字是和设备相关的,所以如果你用了一个不同的设备,你要改变这些地方。
在下个版本里面,我将使用XML做为配置文件。
为了编译,你必须有Microsoft.DirectX.DirectInput.dll文件,你可以在Directx SDK找到它。
工程
要创建一个空服务,你有两个办法:从一个空白的解决方案开始或者使用DssNewService.exe。我将使用较容易的那种:打开MR的控制行窗口然后cd到samples目录。然后启动DssNewService.exe。这将创建一个包括所有必要数据的新工程。这个工程还有一个后构建行为来产生代理类。
另外创建的一个文件是Manifest.xml和PadTypes.cs文件。PadTypes包含了服务需要使用的消息。然后定义你服务要响应的行为。
打开解决方案并向Microsoft.DirectX.DirectInput.dll添加引用。添加:
using Microsoft.DirectX.DirectInput;
你现在可以使用Direct Input了。
服务分成三个逻辑部分:
l Direct Input初始化
三、l Direct Input轮询
l 消息提醒
Direct Input初始化
//private class variable
private Device _gamepad = null;
...
//Init direct input
DeviceList devlist =Manager.GetDevices(DeviceClass.GameControl,
EnumDevicesFlags.AttachedOnly);
foreach (DeviceInstance inst in devlist)
{
Guid g =inst.InstanceGuid;
_gamepad = new Device(g);
_gamepad.SetCooperativeLevel(_form.Handle,
CooperativeLevelFlags.Background |
CooperativeLevelFlags.NonExclusive);
}
if (_gamepad != null)
{
_gamepad.SetDataFormat(DeviceDataFormat.Joystick);
_gamepad.Acquire();
}
现在我们可以用_gamepad查询手柄的状态,去看按键值或者按钮状态。_form仅仅是用来初始化DirectInput的,还可以提供对手柄的图形反馈。
注意!如果我在VS2005里面开始调试,我会得到一个“Loader Lock”的警告。这是Manageddebugging assistants引起的。所以你可以如文章描述那样禁用这个服务,或者运行程序以后再附加上去。
四、Direct Input轮询
手柄询分成2块。第一个块是基于一个计时器:在每次tick读一次手柄的数据,数据会被立刻返回。会激活一个Arbiter,异步委托进程的消息和事件会发送到主网关。
//the timer port
private Port _timerPort = null;
...
_timerPort = new Port();
...
_timerPort.Post(DateTime.Now);
Activate(Arbiter.Receive(true, _timerPort,
delegate(DateTime sign)
{
Activate(//every 75 ms ...
Arbiter.Receive(false, TimeoutPort(75),
delegate(DateTime time)
{
//poll the gamepad device and enqueue the data read
if (_gamepad != null)
{
// ... poll the pad
JoystickState jstate = default(JoystickState);
_gamepad.Poll();
jstate = _gamepad.CurrentJoystickState;
_joyReads.Post(jstate);
}
_timerPort.Post(time);
}
)
);
}
));
另外一个arbiter是在_joyReads被激活的。
Activate(
Arbiter.Receive(true, _joyReads,
delegate(JoystickState jsta)
{
//jsta analysis inorder to send external notifications
})
);
这段代码读当前的按键值(L,R)并把它们归一化到(-1,1)。
RightStickUpdate actR = new RightStickUpdate();
actR.Body.Stick.StickValueX =_state.RightStick.StickValueX;
actR.Body.Stick.StickValueY =_state.RightStick.StickValueY;
LogInfo("RightStickUpdate " +actR.Body.Stick.StickValueX,
actR.Body.Stick.StickValueY);
_mainPort.Post(actR);
RightStickUpdate是一个定义在PadType.cs的类
public class RightStickUpdate :
Update
{
private StickStatus _stick;
[DataMember]
public StickStatusStickStatus
{
get { return _stick; }
set { _stick = value; }
}
public RightStickUpdate()
: base(new StickUpdateResponseRight())
{
_stick= base.Body.Stick;
}
}
最后一步是在消息处理函数里面,如下:
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumeratorRightStickUpdateHandler(RightStickUpdate rsUpd)
{
rsUpd.ResponsePort.Post(DefaultUpdateResponseType.Instance);
//notify it
SendNotification(_submgrPort, rsUpd);
yield break;
}
提交一个反馈并且发送一个通知。在解决方案内的其他消息都是来处理按键和按钮的。
五、GUI
很快会开发GUI来返回手柄状态。为了表现确实是内部服务。PadInfoForm是空的。
using Microsoft.Ccr.Adapters.WinForms;
...
//Create Form
PadInfoForm _form = new PadInfoForm();
WinFormsServicePort.Post(new RunForm(
delegate()
{
return _form;
}
));
下个版本将有一个GUI。如果你有建议或者评论,请提出来!