d.sunnyone.org
sunnyone.org

ページ

2012-09-09

Taoを使ってC#/WPFでゲームパッドのボタンを読み取る

ちょっとしたツールを作るために、ゲームパッドをC#で書いたWPFアプリケーションから読もうと思ったが、意外とすっきりいかなかった。結論としては、Tao Frameworkというフレームワークに含まれるWin32 APIのwrapperを使うことで、気軽に読める。

---

ゲームパッド (Joystick? Joypad?)を読むにはDirectInputが定番らしくて、たしかに簡単そうに見えたのでチャレンジしてみたのだけど、managed DirectXを.NET 4で使うにはapp.configを書き替えないといけないとか、SetCooperativeLevelの第一引数がWindows.Windows.Forms.Controlだったりする罠(HWNDも取れるので実は後述の方法でいけるのだけど)にあったりして、挫折した。

ゲームパッドを読むためのAPIがWin32のwinmmというのにあるらしいとわかったが、P/Invokeの定義を書くのが面倒なので探してみたら、Tao Frameworkというゲーム用のフレームワークに定義されているらしい。調べてみると、MITライセンスなので使うことにした。

簡単な使い方

Joystick系のAPIはTao.Platform.Windowsに入っている。Tao Frameworkにはインストーラもあるのだけど、このdllを参照追加すればいいだけなので、zipを落としてdllだけ横に置くのが簡単でいいと思う。

参照追加したら、JOYINFO構造体をnewして、joyGetPos()というメソッドにjoypad IDをセットで渡せばおわり。IDはただの0からの連番。なお、joyGetNumDevs()は、つながっているデバイスの数を返すわけじゃないから注意だ。

for (int i = 0; i < Tao.Platform.Windows.Winmm.joyGetNumDevs(); i++)
{
    Tao.Platform.Windows.Winmm.JOYINFO joyinfo = new Tao.Platform.Windows.Winmm.JOYINFO();
    if (Tao.Platform.Windows.Winmm.joyGetPos(i, ref joyinfo) == Tao.Platform.Windows.Winmm.JOYERR_NOERROR)
    {
        this.textBlock1.Text += String.Format("Joypad[{0}] detected. Button: {1}\n", i, joyinfo.wButtons);
    }
}
パッドによってはもっと詳しい情報が取れるjoyGetPosEx()もあるらしい。自分の用途にはその必要がなかったので使わなかったけど、このへんのAPIの使い方についてはこのへんに書いてある。

イベント的に読み取る1: タイマーで読み取る

もうちょっと実用的に使うために、押されたことを検知したい。

API的には、joySetCapture()というメソッドがあり、これを使うと、ウィンドウメッセージを飛ばすことができる。しかし、引数からしてポーリングっぽい感じなので、WPFアプリケーションであれば、HWNDとか意識しなくていいので、タイマーで見たほうが素直だと思うのでそうした。実装はこのような感じ。
private void Window_ContentRendered(object sender, EventArgs e)
{
Tao.Platform.Windows.Winmm.JOYINFO joyinfo = new Tao.Platform.Windows.Winmm.JOYINFO();
int lastJoystick = -1;
for (int i = 0; i < Tao.Platform.Windows.Winmm.joyGetNumDevs(); i++)
{
if (Tao.Platform.Windows.Winmm.joyGetPos(i, ref joyinfo) == Tao.Platform.Windows.Winmm.JOYERR_NOERROR)
{
lastJoystick = i;
}
}
if (lastJoystick < 0)
{
this.textBlock1.Text = "Joystick not found.\n";
return;
}
DispatcherTimer timer = new DispatcherTimer(DispatcherPriority.Normal);
timer.Interval = new TimeSpan(0, 0, 0, 0, 10);
bool downState = false;
timer.Tick += new EventHandler((sender1, e1) =>
{
if (Tao.Platform.Windows.Winmm.joyGetPos(lastJoystick, ref joyinfo) == 0)
{
bool nowDown = (joyinfo.wButtons & Tao.Platform.Windows.Winmm.JOY_BUTTON1) != 0;
if (downState && !nowDown)
{
this.textBlock1.Text += "Button 1 UP\n";
downState = false;
}
else if (!downState && nowDown)
{
this.textBlock1.Text += "Button 1 DOWN\n";
downState = true;
}
}
});
timer.Start();
}
view raw readjoypad1.cs hosted with ❤ by GitHub


イベント的読み取る2: joySetCaptureで読み取る

それでもjoySetCaptureが使いたいならば、こんなかんじ。 一応動いた。
private void Window_ContentRendered(object sender, EventArgs e)
{
IntPtr hwnd = new WindowInteropHelper(this).Handle;
HwndSource source = HwndSource.FromHwnd(hwnd);
source.AddHook(new HwndSourceHook(WndProc));
int lastjoystickid = 0; // FIXME: set properly
// 1st argument is int...
Tao.Platform.Windows.Winmm.joySetCapture(hwnd.ToInt32(), lastjoystickid, 100, true);
}
// MM_JOY1MOVE, MM_JOY1ZMOVE, MM_JOY2MOVE, MM_JOY2ZMOVE are defined in Tao.Platform.Windows.Winmm.
private const int MM_JOY1BUTTONDOWN = 0x3B5;
private const int MM_JOY2BUTTONDOWN = 0x3B6;
private const int MM_JOY1BUTTONUP = 0x3B7;
private const int MM_JOY2BUTTONUP = 0x3B8;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == MM_JOY1BUTTONDOWN)
{
Dispatcher.Invoke(new System.Action(() =>
{
textBlock1.Text += "Button down!\n";
}));
}
return IntPtr.Zero;
}
view raw readjoypad2.cs hosted with ❤ by GitHub

0 件のコメント:

コメントを投稿