C++ 鸭科夫手柄适配

使用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(&currentPos);

        // 计算需要移动的相对距离
        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);
}
相关推荐
杨筱毅5 小时前
【C++】【C++面试】Android SO 体积优化技术点梳理
c++·面试
代码AC不AC5 小时前
【C++】哈希表实现 - 链地址法/哈希桶
c++·哈希算法·哈希·哈希桶·链地址法
想不明白的过度思考者5 小时前
Rust——或模式(Or Patterns)的语法:Rust模式匹配的优雅演进
开发语言·后端·rust·模式匹配
绵绵细雨中的乡音5 小时前
深入理解 Rust 的 LinkedList:双向链表的实践与思考
开发语言·链表·rust
dont worry about it5 小时前
使用亮数据爬虫API零门槛快速爬取Tiktok数据
开发语言·爬虫·python
oioihoii5 小时前
Rust 中 LinkedList 的双向链表结构深度解析
开发语言·链表·rust
小杰帅气5 小时前
STL_List简单使用
开发语言·c++·list
清辞8535 小时前
C++数据结构(链表和list)
数据结构·c++·链表
csbysj20205 小时前
C 标准库 - <math.h>
开发语言