上篇介紹「被動模式」(系統定時推送訊息),本篇深入「主動模式」(程式主動輪詢)並解決按鍵重複觸發與組合鍵並發的核心難題。
系統以 uPeriod 間隔推送狀態,人類按下→釋放極短時間內可能收到 N 次「按下」訊息,若直接處理會導致遊戲角色連續移動多步(單次點擊卻觸發多次)。
[DllImport("winmm.dll")]
public static extern int joyGetPos(int uJoyID, ref JOYINFO pji);
[DllImport("winmm.dll")]
public static extern int joyGetPosEx(int uJoyID, ref JOYINFOEX pji);
joyGetPos 僅支援 4 個按鈕,實務建議使用 joyGetPosEx。
[StructLayout(LayoutKind.Sequential)]
public struct JOYINFOEX
{
public int dwSize; // 結構大小,必須初始化
public int dwFlags; // 指定欲取得資料項
public int dwXpos; // X 座標
public int dwYpos; // Y 座標
public int dwZpos; // Z 座標
public int dwRpos; // 第四軸
public int dwUpos; // 第五軸
public int dwVpos; // 第六軸
public int dwButtons; // 32 個按鈕狀態位元組合
public int dwButtonNumber;// 當前按下按鈕編號
public int dwPOV; // 視角控制器角度 (0-35900)
public int dwReserved1;
public int dwReserved2;
}
JOYINFOEX infoEx = new JOYINFOEX();
infoEx.dwSize = Marshal.SizeOf(typeof(JOYINFOEX));
infoEx.dwFlags = (int)JOY_RETURNBUTTONS; // 需取得按鈕狀態
int result = joyGetPosEx(this.Id, ref infoEx);
if (result == JOYERR_NOERROR) // 0 代表成功
{
// 解析 infoEx.dwXpos, dwYpos, dwButtons...
}
Thread.Sleep(uPeriod)System.Windows.Forms.Timer 定時觸發本質上系統被動模式也是內部輪詢,主動模式將控制權交還開發者。
記錄上一輪按下的按鈕集合,本輪僅通知「新增」的按鍵。
previousButtons = 無;
// 死循環輪詢
while (true) {
if (joyGetPosEx(手柄ID, ref joyInfo) == 成功) {
JoyButtons buttons = 取得當前按下按鈕(joyInfo);
if (buttons != 無) {
if (buttons != previousButtons) {
// 本輪按鍵組合與上輪不同 -> 通知
OnClick(buttons);
previousButtons = buttons; // 記錄本輪
}
}
}
Thread.Sleep(uPeriod);
}
場景:長按「右鍵」移動,同時連打「A」、「B」攻擊。
原始狀態序列:
右鍵(1) → 右鍵(2) → 右鍵+A(3) → 右鍵(4) → 右鍵+B(5) → 右鍵(6)...
簡單比對法會誤判右鍵觸發 5 次。正確解法:僅通知「差集」按鍵。
結果:右鍵僅通知 1 次,A、B 各通知 1 次,完美符合人類操作語意。
下一篇將整合鍵盤模擬 API,完成「手柄 → 鍵盤/滑鼠」外掛軟體。請支持回覆!