用最簡單的方式,透過winmm.dll讓c#直接存取系統api,達成遊戲手把的操作.
感覺上C#使用遊戲手把應該是一件很簡單直覺的事情,但錯了...官方並沒有支援的很好,或是說並沒有支援(從我以前踏入C#就常常發現很多這種例子),study過後大概就下面幾種常見方案
1.調用directx mamanged dll來使用,你從很多教學都會看到....但這是爛方法,現在也幾乎不能用了,原因是 .net版本的差異以及無法在x64上執行,這種方法多數是存在於.net framework 2.0 3.5 x86電腦為主流時代的做法,現在還有沒有辦法這樣使用是一個問號,如果真的要這樣做,需要花些時間處理相容問題,不建議.
(不騙你,當初浪費很多時間在這上面,總之不建議.....看到 using directx 之類的教學就別浪費時間了)
2.c#直接call directx unmanaged dll,這也不是不行,前提是你熟悉directx原本c++的操作,以及c#直接存取c++ dll的經驗,道行高的可以試試看,但我覺得即使成功也不是好方法,理論上是一個可行方式,實際上沒看過這樣的教學.
3.第三人家包好的c# sdk,這應該是目前最推薦的方式,最常聽到就是sharpdx或是slimdx以opentk這三款,這算是最穩步的做法,只是如果只是做簡單的小事情,總覺得這些東西肥大累贅,不過如果你的目標是開發遊戲,應該選擇這方案.
4.透過系統API來做 winmm.dll ,這是我選擇的方案,下面會針對方法3.4優缺點提出一些討論.
5.XNA??? 這東西幾年前被微軟拋棄了,要放下,別浪費自己時間.
方法3是透過人家第三方完整的sdk達成(骨子裡多數也是包directx就是),理論上若是要開發一個完整的遊戲,就使用人家第三方sdk吧...不用多想了,但我這邊提醒的是由於遊戲手把不同,針對xbox的遊戲手把有自己一套api,所以你寫的遊戲若是想完整支援所有種類手把,除了一般遊戲手把的api外,還需要處理xinput這東西.
那如果我只是要做一些很簡單的事情,簡單的小2d遊戲,遊戲幾百KB,怎麼相關的SDK組件就好幾MB甚至10多MB塞了一堆東西...看了很礙眼,而且多數東西用不到...就算把相關DLL精簡化留下來的東西還是不小,能不能不要殺雞用牛刀呢?
如果你跟我一樣有類似的想法和狀況,那麼方法四是最佳解答.
這方法還有額外的一個好處,就是不管你是什麼遊戲手把,都是通過相同api介面去讀取,xbox360的xinput 手把資訊一樣可以讀到,缺點是它不像透過directx拿到的裝置一樣,有一個完整的裝置GUID名稱,但手把這種東西也不會換來換去,換了通常使用者也會重新設定一下配置,裝置名稱的嚴謹度似乎也不太會是議題(?).
這邊分享兩篇教學,我的範例是參考下面兩篇教學來的
http://www.cnblogs.com/kingthy/archive/2009/03/25/1421838.html
https://yal.cc/c-sharp-joystick-tracking-via-winmm-dll
以前有使用sharpdx來存取遊戲手把服務的經驗,憑著當時的感覺大概依照那種風格以native api重寫一個小sample,沒包成元件的型式,可以自己來,跟上面那篇大陸教學相比採用不同相同的策略,主要是透過joySetCapture來採集訊息在我一個usb手把轉接器上似乎有些問題,再加上這種方式得透過message訊息交流道winform視窗,還得自己處理event routing,反其道而行採用主動polling的方式來處理,減化很多,也把joySetCapture對我某個usb搖感的相容性問題解決了.
ps.joySetCapture這是一個系統api,正確呼叫後,系統會定時間send message event到你的winform handle去,你要自己去處理,比較不喜歡這種方式就是.
下面這sample大小出來12KB...至於第三方dll檔呢? 一個dll幾MB跑不掉,而且通常最少最少都得引用2.3個並用,乾淨.簡潔.單純.直白,快又有效.....
不過功能當然也是比第三方DLL SDK少上不少,好比說力回饋支援.Z軸3D操作等等或是取得搖感進階硬體資訊,這些就缺乏了,就看你自已的需要有沒有需要去用到那些東西.
https://github.com/erspicu/GamePad
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitJoy();
}
//REF http://www.cnblogs.com/kingthy/archive/2009/03/25/1421838.html
//REFVhttps://yal.cc/c-sharp-joystick-tracking-via-winmm-dll/
[StructLayout(LayoutKind.Sequential)]
public struct JOYCAPS
{
public ushort wMid;
public ushort wPid;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string szPname;
public int wXmin;
public int wXmax;
public int wYmin;
public int wYmax;
public int wZmin;
public int wZmax;
public int wNumButtons;
public int wPeriodMin;
public int wPeriodMax;
public int wRmin;
public int wRmax;
public int wUmin;
public int wUmax;
public int wVmin;
public int wVmax;
public int wCaps;
public int wMaxAxes;
public int wNumAxes;
public int wMaxButtons;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string szRegKey;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szOEMVxD;
}
[StructLayout(LayoutKind.Sequential)]
public struct JOYINFOEX
{
public Int32 dwSize; // Size, in bytes, of this structure.
public Int32 dwFlags; // Flags indicating the valid information returned in this structure.
public Int32 dwXpos; // Current X-coordinate.
public Int32 dwYpos; // Current Y-coordinate.
public Int32 dwZpos; // Current Z-coordinate.
public Int32 dwRpos; // Current position of the rudder or fourth joystick axis.
public Int32 dwUpos; // Current fifth axis position.
public Int32 dwVpos; // Current sixth axis position.
public Int32 dwButtons; // Current state of the 32 joystick buttons (bits)
public Int32 dwButtonNumber; // Current button number that is pressed.
public Int32 dwPOV; // Current position of the point-of-view control (0..35,900, deg*100)
public Int32 dwReserved1; // Reserved; do not use.
public Int32 dwReserved2; // Reserved; do not use.
}
[StructLayout(LayoutKind.Sequential)]
public struct JOYINFO
{
public Int32 wXpos; // Current X-coordinate.
public Int32 wYpos; // Current Y-coordinate.
public Int32 wZpos; // Current Z-coordinate.
public Int32 wButtons; // Current state of joystick buttons.
}
[DllImport("winmm.dll")]
public static extern Int32 joyGetPos(Int32 uJoyID, ref JOYINFO pji);
[DllImport("winmm.dll")]
public static extern Int32 joyGetPosEx(Int32 uJoyID, ref JOYINFOEX pji);
[DllImport("winmm.dll")]
public static extern int joyGetDevCaps(int uJoyID, ref JOYCAPS pjc, int cbjc);
public struct DeviceJoyInfo
{
public bool JoyEx;
public int ButtonCount;
public int ID;
public int Button_old;
public int Way_X_old;
public int Way_Y_old;
}
public struct joystickEvent
{
public int event_type; //0:方向鍵觸發 1:一般按鈕觸發
public int joystick_id;//發生於哪個遊戲手把
public int button_id;//如果是一般按鈕觸發,發生在哪顆按鈕
public int button_event;//0:鬆開 1:壓下
public int way_type; //0:x方向鍵盤 1:y方向鍵盤
public int way_value;
}
List<DeviceJoyInfo> joyinfo_list = new List<DeviceJoyInfo>();
JOYCAPS joycap = new JOYCAPS();
JOYINFO js = new JOYINFO();
JOYINFOEX jsx = new JOYINFOEX();
int JOYCAPS_size;
int PeriodMin = 0;
unsafe public void InitJoy()
{
Stopwatch st = new Stopwatch();
st.Restart();
JOYCAPS_size = Marshal.SizeOf(typeof(JOYCAPS));
for (int i = 0; i < 256; i++)
{
if (joyGetDevCaps(i, ref joycap, JOYCAPS_size) == 0)
{
DeviceJoyInfo info = new DeviceJoyInfo();
//set id
info.ID = i;
//check joyex
if (joyGetPosEx(i, ref jsx) == 0)
{
info.JoyEx = true;
info.Way_X_old = jsx.dwXpos;
info.Way_Y_old = jsx.dwYpos;
}
else if (joyGetPos(i, ref js) == 0)
{
info.JoyEx = false;
info.Way_X_old = js.wXpos;
info.Way_Y_old = js.wYpos;
}
else continue; //裝置功能失效
//set button count
info.ButtonCount = joycap.wNumButtons;
info.Button_old = 0;
if (joycap.wPeriodMin > PeriodMin)
PeriodMin = joycap.wPeriodMin;
joyinfo_list.Add(info);
}
}
//取出所有目前連線遊戲手把中最慢的PeriodMin然後+5ms
PeriodMin += 5;
new Thread(polling_listener).Start();
st.Stop();
Console.WriteLine("init joypad infor : " + st.ElapsedMilliseconds + " ms");
}
List<joystickEvent> joy_event_captur()
{
List<joystickEvent> event_list = new List<joystickEvent>();
for (int i_button = 0; i_button < joyinfo_list.Count(); i_button++)
{
DeviceJoyInfo button_inf = joyinfo_list[i_button];
int button_id = button_inf.ID;
int button_count = button_inf.ButtonCount;
int button_now, X_now, Y_now;
if (button_inf.JoyEx == false)
{
joyGetPos(button_id, ref js);
button_now = js.wButtons;
X_now = js.wXpos;
Y_now = js.wYpos;
}
else
{
joyGetPosEx(button_id, ref jsx);
button_now = jsx.dwButtons;
X_now = jsx.dwXpos;
Y_now = jsx.dwYpos;
}
int button_old = button_inf.Button_old;
int X_old = button_inf.Way_X_old;
int Y_old = button_inf.Way_Y_old;
button_inf.Button_old = button_now;
button_inf.Way_X_old = X_now;
button_inf.Way_Y_old = Y_now;
joyinfo_list[i_button] = button_inf;
if (button_old != button_now || button_now != 0)
{
for (int i = 0; i < button_count; i++)
{
if ((button_now & 1) != 0)
{
joystickEvent event_item = new joystickEvent();
event_item.event_type = 1;
event_item.joystick_id = button_inf.ID;
event_item.button_id = i + 1;
event_item.button_event = 1;
event_list.Add(event_item);
}
else
{
if ((button_now & 1) != (button_old & 1))
{
joystickEvent event_item = new joystickEvent();
event_item.event_type = 1;
event_item.joystick_id = button_inf.ID;
event_item.button_id = i + 1;
event_item.button_event = 0;
event_list.Add(event_item);
}
}
button_now >>= 1;
button_old >>= 1;
}
}
if (X_old != X_now || (X_now != 32767 && X_now != 32511 && X_now != 32254))
{
if ((X_now != 32767 && X_now != 32511))
{
joystickEvent event_item = new joystickEvent();
event_item.event_type = 0;
event_item.joystick_id = button_inf.ID;
event_item.way_type = 0;
event_item.way_value = X_now;
event_list.Add(event_item);
}
else
{
joystickEvent event_item = new joystickEvent();
event_item.event_type = 0;
event_item.joystick_id = button_inf.ID;
event_item.way_type = 0;
event_item.way_value = X_now;
event_list.Add(event_item);
}
}
if (Y_old != Y_now || (Y_now != 32767 && Y_now != 32511 && Y_now != 32254))
{
if ((Y_now != 32767 && Y_now != 32511))
{
joystickEvent event_item = new joystickEvent();
event_item.event_type = 0;
event_item.joystick_id = button_inf.ID;
event_item.way_type = 1;
event_item.way_value = Y_now;
event_list.Add(event_item);
}
else
{
joystickEvent event_item = new joystickEvent();
event_item.event_type = 0;
event_item.joystick_id = button_inf.ID;
event_item.way_type = 1;
event_item.way_value = Y_now;
event_list.Add(event_item);
}
}
}
return event_list;
}
bool app_running = true;
void polling_listener()
{
while (app_running)
{
Thread.Sleep(PeriodMin);
List<joystickEvent> event_list = joy_event_captur();
foreach (joystickEvent joy_event in event_list)
{
if (joy_event.event_type == 0) //方向鍵觸發
{
if (joy_event.way_type == 0) //x
{
Console.WriteLine("裝置 " + joy_event.joystick_id + " ,X " + joy_event.way_value);
}
else//y
{
Console.WriteLine("裝置 " + joy_event.joystick_id + " ,Y " + joy_event.way_value);
}
}
else //一般按鈕觸發
{
if (joy_event.button_event == 0)
{
Console.WriteLine("裝置 " + joy_event.joystick_id + " ,按鈕 " + joy_event.button_id + " 放開");
}
else
{
Console.WriteLine("裝置 " + joy_event.joystick_id + " ,按鈕 " + joy_event.button_id + " 壓下");
}
}
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
app_running = false;
}
}
}