使用Win项目编译
不想自己编译的,可以下载
通过网盘分享的文件:手柄键位映射.exe
链接: https://pan.baidu.com/s/1N5mQMkqmqwT-1FaIA8hUxg?pwd=5c7v 提取码: 5c7v
cpp
//Ctrl.h
#pragma once
#include <Windows.h>
#include <Xinput.h>
#include <iostream>
#include <thread>
#include <atomic>
#define TRIGGER_THRESHOLD 30
#define RIGHT_THUMB_DEADZONE 8689
#define SMOOTHING_FACTOR 0.7f
#define MOUSE_SENSITIVITY 0.2f
#pragma comment(lib, "Xinput.lib")
#pragma comment(lib, "User32.lib")
class GamepadMapper {
private:
std::atomic<bool> running;
// 死区阈值,避免摇杆轻微漂移
const int LEFT_THUMB_DEADZONE = 7849; // XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE
// 按键状态跟踪
bool lastjiaState = false;
bool lastjianState = false;
bool lastBState = false;
bool lastYState = false;
bool lastAState = false;
bool lastRTState = false;
bool lastLTState = false;
bool lastRSButtonState = false;
bool lastLSButtonState = false;
bool lastXState = false;
bool LState = false;
bool RState = false;
bool UState = false;
bool DState = false;
POINT screenCenter;
int max_mm = 0;
int now_max_mm = 0;
public:
GamepadMapper() : running(true) {
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
screenCenter.x = screenWidth / 2;
screenCenter.y = screenHeight / 2 - (screenHeight / 20);
max_mm = (screenHeight / 2) - (screenHeight / 8);
now_max_mm = max_mm;
}
~GamepadMapper() {
stop();
}
void stop() {
running = false;
}
// 模拟键盘按键
void simulateKey(WORD key, bool press) {
INPUT input = { 0 };
input.type = INPUT_KEYBOARD;
// 尝试获取扫描码
UINT scanCode = MapVirtualKey(key, MAPVK_VK_TO_VSC);
if (scanCode != 0) {
input.ki.wScan = scanCode;
input.ki.dwFlags = KEYEVENTF_SCANCODE | (press ? 0 : KEYEVENTF_KEYUP);
}
else {
// 回退到虚拟键码
input.ki.wVk = key;
input.ki.wScan = 0;
input.ki.dwFlags = press ? 0 : KEYEVENTF_KEYUP;
}
input.ki.time = 0;
input.ki.dwExtraInfo = 0;
SendInput(1, &input, sizeof(INPUT));
}
// 模拟鼠标按键
void simulateMouseClick(DWORD flags, bool press) {
INPUT input = { 0 };
input.type = INPUT_MOUSE;
input.mi.dwFlags = press ? flags : (flags == MOUSEEVENTF_RIGHTDOWN ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_LEFTUP);
input.mi.time = 0;
input.mi.dwExtraInfo = 0;
SendInput(1, &input, sizeof(INPUT));
}
float smoothX = 0.0f;
float smoothY = 0.0f;
void processRightThumbstickForMouse_Line(SHORT x, SHORT y) {
// 应用死区
if (abs(x) < RIGHT_THUMB_DEADZONE) x = 0;
if (abs(y) < RIGHT_THUMB_DEADZONE) y = 0;
// 如果摇杆在死区内,重置平滑值
if (x == 0 && y == 0) {
smoothX = 0.0f;
smoothY = 0.0f;
return;
}
// 将摇杆值归一化到 [-1, 1] 范围
float normalizedX = static_cast<float>(x) / 32767.0f;
float normalizedY = static_cast<float>(y) / 32767.0f;
// 应用平滑滤波
smoothX = SMOOTHING_FACTOR * smoothX + (1.0f - SMOOTHING_FACTOR) * normalizedX;
smoothY = SMOOTHING_FACTOR * smoothY - (1.0f - SMOOTHING_FACTOR) * normalizedY;
// 计算鼠标移动距离(应用灵敏度)
int mouseMoveX = static_cast<int>(smoothX * MOUSE_SENSITIVITY * 100);
int mouseMoveY = static_cast<int>(smoothY * MOUSE_SENSITIVITY * 100);
// 移动鼠标
if (mouseMoveX != 0 || mouseMoveY != 0) {
INPUT input = { 0 };
input.type = INPUT_MOUSE;
input.mi.dx = mouseMoveX;
input.mi.dy = mouseMoveY;
input.mi.dwFlags = MOUSEEVENTF_MOVE;
input.mi.time = 0;
input.mi.dwExtraInfo = 0;
SendInput(1, &input, sizeof(INPUT));
}
}
bool lock_mouse = true;
void processRightThumbstickForMouse(SHORT x, SHORT y) {
// 应用死区
if (abs(x) < RIGHT_THUMB_DEADZONE) x = 0;
if (abs(y) < RIGHT_THUMB_DEADZONE) y = 0;
// 如果摇杆在死区内,不移动
if (x == 0 && y == 0) {
return;
}
// 将摇杆值归一化到 [-1, 1] 范围
float normalizedX = static_cast<float>(x) / 32767.0f;
float normalizedY = static_cast<float>(y) / 32767.0f;
// 计算摇杆输入对应的角度
float inputAngle = atan2(normalizedY, normalizedX);
// 计算圆形轨迹上的目标位置
int targetX = screenCenter.x + static_cast<int>(now_max_mm * cos(inputAngle));
int targetY = screenCenter.y - static_cast<int>(now_max_mm * sin(inputAngle));
// 获取当前鼠标位置
POINT currentPos;
GetCursorPos(¤tPos);
// 计算需要移动的相对距离
int deltaX = targetX - currentPos.x;
int deltaY = targetY - currentPos.y;
// 应用插值平滑 - 使用缓动函数让移动更柔和
const float SMOOTH_FACTOR = 0.1f; // 调整这个值来控制平滑程度 (0.1-0.5)
// 计算平滑后的移动距离
int smoothDeltaX = static_cast<int>(deltaX * SMOOTH_FACTOR);
int smoothDeltaY = static_cast<int>(deltaY * SMOOTH_FACTOR);
// 确保至少移动1像素,避免微小移动被忽略
if (abs(smoothDeltaX) < 1 && abs(deltaX) > 0) {
smoothDeltaX = (deltaX > 0) ? 1 : -1;
}
if (abs(smoothDeltaY) < 1 && abs(deltaY) > 0) {
smoothDeltaY = (deltaY > 0) ? 1 : -1;
}
// 移动鼠标
if (smoothDeltaX != 0 || smoothDeltaY != 0) {
INPUT input = { 0 };
input.type = INPUT_MOUSE;
input.mi.dx = smoothDeltaX;
input.mi.dy = smoothDeltaY;
input.mi.dwFlags = MOUSEEVENTF_MOVE;
input.mi.time = 0;
input.mi.dwExtraInfo = 0;
SendInput(1, &input, sizeof(INPUT));
}
}
// 处理左摇杆输入
void processLeftThumbstick(SHORT x, SHORT y) {
// 应用死区
if (abs(x) < LEFT_THUMB_DEADZONE) x = 0;
if (abs(y) < LEFT_THUMB_DEADZONE) y = 0;
// 映射到WASD
bool sPressed = (y < -LEFT_THUMB_DEADZONE);
bool wPressed = (y > LEFT_THUMB_DEADZONE);
bool aPressed = (x < -LEFT_THUMB_DEADZONE);
bool dPressed = (x > LEFT_THUMB_DEADZONE);
// 发送按键事件
static bool lastW = false, lastA = false, lastS = false, lastD = false;
if (wPressed != lastW) {
simulateKey('W', wPressed);
lastW = wPressed;
}
if (sPressed != lastS) {
simulateKey('S', sPressed);
lastS = sPressed;
}
if (aPressed != lastA) {
simulateKey('A', aPressed);
lastA = aPressed;
}
if (dPressed != lastD) {
simulateKey('D', dPressed);
lastD = dPressed;
}
}
void processTriggers(const XINPUT_GAMEPAD& gamepad) {
bool rtPressed = (gamepad.bRightTrigger > TRIGGER_THRESHOLD);
if (rtPressed != lastRTState) {
simulateMouseClick(MOUSEEVENTF_LEFTDOWN, rtPressed);
lastRTState = rtPressed;
}
rtPressed = (gamepad.bLeftTrigger > TRIGGER_THRESHOLD);
if (rtPressed != lastLTState) {
simulateMouseClick(MOUSEEVENTF_RIGHTDOWN, rtPressed);
lastLTState = rtPressed;
}
// 如果需要,也可以处理LT键
// bool ltPressed = (gamepad.bLeftTrigger > TRIGGER_THRESHOLD);
// 处理LT键映射...
}
// 处理按钮输入
WORD now_number = 0x31;
void processButtons(const XINPUT_GAMEPAD& gamepad) {
// B键 -> Shift
bool bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_B);
if (bPressed != lastBState) {
simulateKey(VK_LSHIFT, bPressed);
lastBState = bPressed;
}
bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_START);
if (bPressed != lastjiaState) {
simulateKey(VK_TAB, bPressed);
lastjiaState = bPressed;
}
bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_BACK);
if (bPressed != lastjianState) {
simulateKey(0x4D, bPressed);
lastjianState = bPressed;
}
bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_X);
if (bPressed != lastXState) {
simulateKey(VK_SPACE, bPressed);
lastXState = bPressed;
}
bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_Y);
if (bPressed != lastYState) {
now_number = 0x31;
simulateKey(0x56, bPressed);
lastYState = bPressed;
}
bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER);
if (bPressed != lastLSButtonState) {
simulateKey(0x52, bPressed);
lastLSButtonState = bPressed;
}
bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT);
if (bPressed != LState) {
if (bPressed)
{
now_number--;
if (now_number < 0x31)
now_number = 0x31;
}
simulateKey(now_number, bPressed);
LState = bPressed;
}
bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT);
if (bPressed != RState) {
if (bPressed)
{
now_number++;
if (now_number > 0x38)
now_number = 0x38;
}
simulateKey(now_number, bPressed);
RState = bPressed;
}
bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP);
if (bPressed != UState) {
//simulateKey(0x59, bPressed);
if (bPressed)
{
now_max_mm += 3;
if (now_max_mm > max_mm)
now_max_mm = max_mm;
}
UState = bPressed;
}
bPressed = (gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN);
if (bPressed != UState) {
//simulateKey(0x43, bPressed);
if (bPressed)
{
now_max_mm -= 3;
if (now_max_mm < 40)
now_max_mm = 40;
}
UState = bPressed;
}
// Y键 -> 鼠标右键
//bool yPressed = (gamepad.wButtons & XINPUT_GAMEPAD_Y);
//if (yPressed != lastYState) {
// //simulateMouseClick(MOUSEEVENTF_RIGHTDOWN, yPressed);
// simulateMouseClick(MOUSEEVENTF_LEFTDOWN, yPressed);
// lastYState = yPressed;
//}
bool rsPressed = (gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER);
if (rsPressed != lastRSButtonState) {
if (rsPressed)
{
lock_mouse = !lock_mouse;
if (lock_mouse)
Beep(1000, 200);
else
Beep(800, 300);
}
lastRSButtonState = rsPressed;
}
// A键 -> F键
bool aPressed = (gamepad.wButtons & XINPUT_GAMEPAD_A);
if (aPressed != lastAState) {
simulateKey('F', aPressed);
lastAState = aPressed;
}
}
void run() {
while (running) {
for (DWORD i = 0; i < XUSER_MAX_COUNT && running; i++) {
XINPUT_STATE state;
ZeroMemory(&state, sizeof(XINPUT_STATE));
DWORD result = XInputGetState(i, &state);
if (result == ERROR_SUCCESS) {
// 处理左摇杆
processLeftThumbstick(state.Gamepad.sThumbLX, state.Gamepad.sThumbLY);
// 处理按钮
processButtons(state.Gamepad);
processTriggers(state.Gamepad);
if (lock_mouse)
processRightThumbstickForMouse(
state.Gamepad.sThumbRX,
state.Gamepad.sThumbRY
);
else
processRightThumbstickForMouse_Line(
state.Gamepad.sThumbRX,
state.Gamepad.sThumbRY
);
}
}
// 降低CPU使用率
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// 释放所有按键
releaseAllKeys();
}
private:
void releaseAllKeys() {
// 释放所有可能按下的按键
simulateKey('W', false);
simulateKey('A', false);
simulateKey('S', false);
simulateKey('D', false);
simulateKey(VK_LSHIFT, false);
simulateKey(VK_SPACE, false);
simulateKey('F', false);
simulateKey(0x52, false);
simulateKey(0x56, false);
simulateKey(0x59, false);
simulateKey(0x43, false);
simulateMouseClick(MOUSEEVENTF_RIGHTDOWN, false);
simulateMouseClick(MOUSEEVENTF_LEFTDOWN, false);
}
};
// 控制台事件处理
BOOL WINAPI ConsoleHandler(DWORD signal) {
if (signal == CTRL_C_EVENT) {
std::cout << "\n收到退出信号,程序即将退出..." << std::endl;
return TRUE;
}
return FALSE;
}
cpp
//主函数
#include <windows.h>
#include <string>
#include"resource.h"
#include"Ctrl.h"
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
GamepadMapper mapper;
std::thread* mapperThread;
void runMapper(GamepadMapper& mapper) {
mapper.run();
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 窗口类名
const wchar_t CLASS_NAME[] = L"Sample Window Class";
// 注册窗口类
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
RegisterClass(&wc);
// 计算窗口大小(包含标题栏和边框)
RECT rect = { 0, 0, 420, 400 };
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
// 创建窗口
HWND hwnd = CreateWindowEx(
0, // 扩展窗口样式
CLASS_NAME, // 窗口类
L"逃离鸭科夫-手柄映射", // 窗口标题
WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_SIZEBOX, // 窗口样式:去掉最大化和调整大小
CW_USEDEFAULT, CW_USEDEFAULT, // 位置
width, height, // 大小
NULL, // 父窗口
NULL, // 菜单
hInstance, // 实例句柄
NULL // 附加数据
);
if (hwnd == NULL)
{
return 0;
}
// 显示窗口
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// 在单独线程中运行映射器
std::thread mapperThread1(runMapper, std::ref(mapper));
mapperThread = &mapperThread1;
// 消息循环
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 获取客户区大小
RECT clientRect;
GetClientRect(hwnd, &clientRect);
// 设置文本颜色和背景模式
SetTextColor(hdc, RGB(0, 0, 0)); // 黑色文字
SetBkMode(hdc, TRANSPARENT); // 透明背景
// 创建字体
HFONT hFont = CreateFont(
24, // 字体高度
0, // 字体宽度
0, // 文本倾斜度
0, // 字体倾斜度
FW_NORMAL, // 字体粗细
FALSE, // 斜体
FALSE, // 下划线
FALSE, // 删除线
DEFAULT_CHARSET, // 字符集
OUT_DEFAULT_PRECIS, // 输出精度
CLIP_DEFAULT_PRECIS, // 裁剪精度
DEFAULT_QUALITY, // 输出质量
DEFAULT_PITCH | FF_DONTCARE, // 字体系列
L"Arial" // 字体名称
);
// 选择字体到设备上下文
HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
// 要显示的文本 - 可以包含换行符
const wchar_t* text = L"左摇杆 -> 控制移动\r\n右摇杆 -> 控制射击方向\r\nB键 -> 奔跑\r\nY键 -> 近战武器\r\nA键 -> 交互\r\nX键 -> 翻滚\r\n减号 -> 地图(再按一次关闭)\r\n加号 -> 背包(再按一次关闭)\r\n左键 -> 数字相减(切换武器)\r\n右键 -> 数字相加(切换武器)\r\n左扳机键(LT) -> 瞄准\r\n右扳机键(RT) -> 开火\r\n左长条键(LB) -> 换弹\r\n右长条键(RB) -> 切换准心模式";
// 计算文本矩形(留出一些边距)
RECT textRect = clientRect;
textRect.left += 20;
textRect.right -= 20;
textRect.top += 20;
textRect.bottom -= 20;
// 使用DrawText绘制文本,支持自动换行
DrawText(hdc,
text,
-1, // 自动计算文本长度
&textRect,
DT_VCENTER | // 垂直居中
DT_WORDBREAK | // 自动换行
DT_NOPREFIX); // 不处理&字符
// 恢复原来的字体并删除新字体
SelectObject(hdc, hOldFont);
DeleteObject(hFont);
EndPaint(hwnd, &ps);
}
return 0;
case WM_DESTROY:
mapper.stop();
mapperThread->join();
PostQuitMessage(0);
return 0;
case WM_GETMINMAXINFO:
{
// 限制窗口最小大小,使其不能调整大小
LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam;
RECT rect = { 0, 0, 420, 400 };
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_SIZEBOX, FALSE);
lpMMI->ptMinTrackSize.x = rect.right - rect.left;
lpMMI->ptMinTrackSize.y = rect.bottom - rect.top;
lpMMI->ptMaxTrackSize.x = rect.right - rect.left;
lpMMI->ptMaxTrackSize.y = rect.bottom - rect.top;
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}