2016年4月29日金曜日

C#でジョイパッド入力

C#でジョイパッド入力の方法はいくつか有り、例えばXNAやDirectX SDKを使用する方法があります。しかしXNAはすでに開発が終了し、対応したジョイパッドはXbox360コントローラしかありません。後者の方法は情報も比較的多めですが、SDKの配布が終了?していたり、僕の環境では動かなかったりと、いろいろ問題が有ります。
ということで今回はDLLを使用して読み取ってみました。

とりあえず当初の目標であるXbox360以外のジョイスティックも動作確認しました。おそらくWindowsがゲームコントローラとして認識できるやつなら読み込めるはずです。

一応汎用的なClassと、Xbox360ゲームコントローラ向けのClass(汎用を継承)を用意しています。
注意点として、XNAではLeftShoulderとRightShoulderと、トリガー入力を左右で読み込めますが、DLLを叩いた場合は左右で1軸となり、両方のトリガーを引くと0となり、両方のトリガーを放した状態と区別できません。左右別々に引いた場合は正負で判断できるので、同時にトリガーが両方共引かれることのない操作方法を考える必要があります(例えばフライトシミュでトリガーをラダーに割当、ラダーを両方操作するとエアブレーキ みたいなUIは不可能です)。
その他はXNAと同様のデータを扱えます。もちろん真ん中の椎茸キーが使用できないのも同じです。

以下のクラスを使用している分には、ジョイパッドの素性を把握することはできません。そのコントローラがXbox360のコントローラなのか、別の製品なのか、ということだけでなく、そのジョイパッドに搭載されている軸数・ボタン数も把握できません。このクラスでは最大6軸、32ボタン、1POVまで対応できます。一応dwButtonNumberというそれらしい変数は有りますが、これはcurrent button number pressedと書かれている通り、現在押されているボタンの数を示しているに過ぎません。
一応このクラスを拡張してXbox360に特化したり、他のジョイパッドに特化したりもできますが、必ずしも対応したジョイパッドが接続されているとは限らないことに注意して下さい。

それとアナログスティックについてですが、基本的にXbox360のリターンスプリングは信用できません。-1から+1の範囲に正規化した場合、スティックのセンターが0.25当たりになる場合があります。±0.3未満は読み捨てるか、OverGのように不感帯を調整できるようにするか、もしくはBF3のように気にせずにセンターでも移動するか、いずれかを選択する必要があります。大抵のジョイスティックは±200も暴れることはなく、もっと狭い範囲に収まると思いますが、それでもセンターが0になるとは限りません。

POVの単位はmilli degreeです。上が0、右が90000、下が180000、左が270000で、右上などの中間はそれぞれの中間値となります。POVが入力されていない場合は65535となります。定数で上下左右は判断できますが、中間値を判断する場合は自分で値を書く必要があります(もしくは中間値の定数を追加しましょう)。


参考にしたページ等
ゲームパッド(ジョイパッドと言うらしい)を使ってみる
 ゲームパッド読み取り全般

構造体のポインタを引数に受け取る関数の呼び出し - はらぐろブログラマン
 C#でDLLを叩く方法とか

XNA Game Studioで始めるゲームプログラミング(ISBN:9784048700283)
 Xbox360ゲームコントローラのスイッチ名とか

Windowsに入ってるmmsyscom.h
 構造体の中身他




using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace JoypadCS
{
    class Program
    {
        static void Main(string[] args)
        {
            Xbox360_JoyPad pad = new Xbox360_JoyPad();
            uint padIndex = 0;

            padIndex = 1;
            // テスト環境でXbox360コントローラが1のため決め打ち 
            // 他の環境や別のジョイパッドを使用する場合は変更するか以下の入力フォームを使用 

            /*
            while (true)
            {
                uint[] pads = pad.GetJoypads();
                foreach (uint p in pads)
                { Console.WriteLine("ジョイパッド:" + p); }

                Console.Write("パッドを選択して下さい:");

                string line = Console.ReadLine();
                if (line == "quit") { return; }

                if (!uint.TryParse(line, out padIndex))
                { continue; }

                break;
            }
            //*/

            while (true)
            {
                System.Threading.Thread.Sleep(100);

                JoyPad.JOYERR err = pad.GetPosEx(padIndex);
                JoyPad.JOYINFOEX ex = pad.JoyInfoEx;

                if (err != JoyPad.JOYERR.NOERROR)
                {
                    Console.WriteLine("エラー");
                    continue;
                }

                Console.Write(" " + pad.LeftStickX.ToString("+0.000;-0.000; 0.000"));
                Console.Write(" " + pad.LeftStickY.ToString("+0.000;-0.000; 0.000"));
                Console.Write(" " + pad.RightStickX.ToString("+0.000;-0.000; 0.000"));
                Console.Write(" " + pad.RightStickY.ToString("+0.000;-0.000; 0.000"));
                Console.Write(" " + pad.Trigger.ToString("+0.000;-0.000; 0.000"));

                Console.Write(" ");
                Console.Write(pad.ButtonA ? "*" : "-");
                Console.Write(pad.ButtonB ? "*" : "-");
                Console.Write(pad.ButtonX ? "*" : "-");
                Console.Write(pad.ButtonY ? "*" : "-");
                Console.Write(pad.ButtonLeftShoulder? "*" : "-");
                Console.Write(pad.ButtonRightShoulder ? "*" : "-");
                Console.Write(pad.ButtonBack ? "*" : "-");
                Console.Write(pad.ButtonStart ? "*" : "-");
                Console.Write(pad.ButtonLeftStick ? "*" : "-");
                Console.Write(pad.ButtonRightStick ? "*" : "-");

                Console.WriteLine();
            }
        }

        class JoyPad
        {
            protected IntPtr ptr;
            public JOYINFOEX JoyInfoEx;

            public JoyPad()
            {
                JoyInfoEx = new JOYINFOEX();
                JoyInfoEx.dwSize = (uint)Marshal.SizeOf(typeof(JOYINFOEX));
                JoyInfoEx.dwFlags = JOY_RETURNALL;

                ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(JOYINFOEX)));

                Marshal.StructureToPtr(JoyInfoEx, ptr, false);
            }

            ~JoyPad()
            {
                Marshal.FreeCoTaskMem(ptr);
            }

            public JOYERR GetPosEx(uint JoyID)
            {
                JOYERR err = joyGetPosEx(JoyID, ptr);

                JoyInfoEx = (JOYINFOEX)Marshal.PtrToStructure(ptr, JoyInfoEx.GetType());

                return (err);
            }

            public uint[] GetJoypads()
            {
                List<uint> pads = new List<uint>();
                uint end = joyGetNumDevs();

                for (uint i = 0; i < end; i++)
                {
                    if (GetPosEx(i) != JOYERR.NOERROR)
                    { continue; }
                    pads.Add(i);
                }

                return (pads.ToArray());
            }

            [DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
            protected static extern uint joyGetNumDevs();

            [DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
            protected static extern JOYERR joyGetPosEx(uint uJoyID, IntPtr pji);

            [StructLayout(LayoutKind.Sequential)]
            public struct JOYINFOEX
            {
                public UInt32 dwSize;                /* size of structure */
                public UInt32 dwFlags;               /* flags to indicate what to return */
                public UInt32 dwXpos;                /* x position */
                public UInt32 dwYpos;                /* y position */
                public UInt32 dwZpos;                /* z position */
                public UInt32 dwRpos;                /* rudder/4th axis position */
                public UInt32 dwUpos;                /* 5th axis position */
                public UInt32 dwVpos;                /* 6th axis position */
                public UInt32 dwButtons;             /* button states */
                public UInt32 dwButtonNumber;        /* current button number pressed */
                public UInt32 dwPOV;                 /* point of view state */
                public UInt32 dwReserved1;           /* reserved for communication between winmm & driver */
                public UInt32 dwReserved2;           /* reserved for future expansion */
            }

            /* joystick error return values */
            public enum JOYERR
            {
                NOERROR = (0),               /* no error */
                PARMS = (160 + 5),           /* bad parameters */
                NOCANDO = (160 + 6),         /* request not completed */
                UNPLUGGED = (160 + 7),       /* joystick is unplugged */
            }


            /* public constants used with JOYINFO and JOYINFOEX structures and MM_JOY* messages */
            public const UInt32 JOY_BUTTON1 = 0x0001;
            public const UInt32 JOY_BUTTON2 = 0x0002;
            public const UInt32 JOY_BUTTON3 = 0x0004;
            public const UInt32 JOY_BUTTON4 = 0x0008;
            public const UInt32 JOY_BUTTON1CHG = 0x0100;
            public const UInt32 JOY_BUTTON2CHG = 0x0200;
            public const UInt32 JOY_BUTTON3CHG = 0x0400;
            public const UInt32 JOY_BUTTON4CHG = 0x0800;

            /* public constants used with JOYINFOEX */
            public const UInt32 JOY_BUTTON5 = 0x00000010;
            public const UInt32 JOY_BUTTON6 = 0x00000020;
            public const UInt32 JOY_BUTTON7 = 0x00000040;
            public const UInt32 JOY_BUTTON8 = 0x00000080;
            public const UInt32 JOY_BUTTON9 = 0x00000100;
            public const UInt32 JOY_BUTTON10 = 0x00000200;
            public const UInt32 JOY_BUTTON11 = 0x00000400;
            public const UInt32 JOY_BUTTON12 = 0x00000800;
            public const UInt32 JOY_BUTTON13 = 0x00001000;
            public const UInt32 JOY_BUTTON14 = 0x00002000;
            public const UInt32 JOY_BUTTON15 = 0x00004000;
            public const UInt32 JOY_BUTTON16 = 0x00008000;
            public const UInt32 JOY_BUTTON17 = 0x00010000;
            public const UInt32 JOY_BUTTON18 = 0x00020000;
            public const UInt32 JOY_BUTTON19 = 0x00040000;
            public const UInt32 JOY_BUTTON20 = 0x00080000;
            public const UInt32 JOY_BUTTON21 = 0x00100000;
            public const UInt32 JOY_BUTTON22 = 0x00200000;
            public const UInt32 JOY_BUTTON23 = 0x00400000;
            public const UInt32 JOY_BUTTON24 = 0x00800000;
            public const UInt32 JOY_BUTTON25 = 0x01000000;
            public const UInt32 JOY_BUTTON26 = 0x02000000;
            public const UInt32 JOY_BUTTON27 = 0x04000000;
            public const UInt32 JOY_BUTTON28 = 0x08000000;
            public const UInt32 JOY_BUTTON29 = 0x10000000;
            public const UInt32 JOY_BUTTON30 = 0x20000000;
            public const UInt32 JOY_BUTTON31 = 0x40000000;
            public const UInt32 JOY_BUTTON32 = 0x80000000;

            /* public constants used with JOYINFOEX structure */
            public const UInt32 JOY_POVCENTERED = 65535;
            public const UInt32 JOY_POVFORWARD = 0;
            public const UInt32 JOY_POVRIGHT = 9000;
            public const UInt32 JOY_POVBACKWARD = 18000;
            public const UInt32 JOY_POVLEFT = 27000;

            public const UInt32 JOY_RETURNX = 0x00000001;
            public const UInt32 JOY_RETURNY = 0x00000002;
            public const UInt32 JOY_RETURNZ = 0x00000004;
            public const UInt32 JOY_RETURNR = 0x00000008;
            public const UInt32 JOY_RETURNU = 0x00000010;     /* axis 5 */
            public const UInt32 JOY_RETURNV = 0x00000020;     /* axis 6 */
            public const UInt32 JOY_RETURNPOV = 0x00000040;
            public const UInt32 JOY_RETURNBUTTONS = 0x00000080;
            public const UInt32 JOY_RETURNRAWDATA = 0x00000100;
            public const UInt32 JOY_RETURNPOVCTS = 0x00000200;
            public const UInt32 JOY_RETURNCENTERED = 0x00000400;
            public const UInt32 JOY_USEDEADZONE = 0x00000800;
            public const UInt32 JOY_RETURNALL =
                (JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ |
                JOY_RETURNR | JOY_RETURNU | JOY_RETURNV |
                JOY_RETURNPOV | JOY_RETURNBUTTONS);
            public const UInt32 JOY_CAL_READALWAYS = 0x00010000;
            public const UInt32 JOY_CAL_READXYONLY = 0x00020000;
            public const UInt32 JOY_CAL_READ3 = 0x00040000;
            public const UInt32 JOY_CAL_READ4 = 0x00080000;
            public const UInt32 JOY_CAL_READXONLY = 0x00100000;
            public const UInt32 JOY_CAL_READYONLY = 0x00200000;
            public const UInt32 JOY_CAL_READ5 = 0x00400000;
            public const UInt32 JOY_CAL_READ6 = 0x00800000;
            public const UInt32 JOY_CAL_READZONLY = 0x01000000;
            public const UInt32 JOY_CAL_READRONLY = 0x02000000;
            public const UInt32 JOY_CAL_READUONLY = 0x04000000;
            public const UInt32 JOY_CAL_READVONLY = 0x08000000;

            /* joystick ID public constants */
            public const UInt32 JOYSTICKID1 = 0;
            public const UInt32 JOYSTICKID2 = 1;

            /* joystick driver capabilites */
            public const UInt32 JOYCAPS_HASZ = 0x0001;
            public const UInt32 JOYCAPS_HASR = 0x0002;
            public const UInt32 JOYCAPS_HASU = 0x0004;
            public const UInt32 JOYCAPS_HASV = 0x0008;
            public const UInt32 JOYCAPS_HASPOV = 0x0010;
            public const UInt32 JOYCAPS_POV4DIR = 0x0020;
            public const UInt32 JOYCAPS_POVCTS = 0x0040;
        }

        class Xbox360_JoyPad : JoyPad
        {
            public bool ButtonA
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON1) != 0); } }

            public bool ButtonB
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON2) != 0); } }

            public bool ButtonX
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON3) != 0); } }

            public bool ButtonY
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON4) != 0); } }

            public bool ButtonLeftShoulder
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON5) != 0); } }

            public bool ButtonRightShoulder
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON6) != 0); } }

            public bool ButtonBack
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON7) != 0); } }

            public bool ButtonStart
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON8) != 0); } }

            public bool ButtonLeftStick
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON9) != 0); } }

            public bool ButtonRightStick
            { get { return ((JoyInfoEx.dwButtons & JOY_BUTTON10) != 0); } }
            
            public float LeftStickX
            { get { return (((float)JoyInfoEx.dwXpos - 32767) / 32768); } }

            public float LeftStickY
            { get { return (((float)JoyInfoEx.dwYpos - 32767) / 32768); } }

            public float RightStickX
            { get { return (((float)JoyInfoEx.dwUpos - 32767) / 32768); } }

            public float RightStickY
            { get { return (((float)JoyInfoEx.dwRpos - 32767) / 32768); } }
            
            public float Trigger
            { get { return (((float)JoyInfoEx.dwZpos - 32767) / 32768); } }
        }
    }
}

0 件のコメント:

コメントを投稿