Chapter 119 · 输入系统

手柄输入

GamepadConnected / GetState / ButtonDown / Axis 支持 Xbox/PS 手柄,含按键、摇杆、扳机全状态。

119

概念

XGE 手柄 API 支持最多 4 个手柄(索引 0~3):
• 按键:A/B/X/Y、LB/RB、Start/Back、DPad 等
• 摇杆:左/右摇杆 X/Y 轴(-1.0 ~ 1.0)
• 扳机:LT/RT 模拟量(0.0 ~ 1.0)
支持热插拔检测(Connected)和虚拟手柄注入(SetState)。

API 速查

函数说明
int xgeGamepadConnected(int iGamepad)手柄是否连接
int xgeGamepadGetState(int iGamepad, xge_gamepad_state_t* pState)获取完整状态
int xgeGamepadButtonDown(int iGamepad, uint32_t iButton)按键是否按下
int xgeGamepadButtonPressed(int iGamepad, uint32_t iButton)本帧是否刚按下
int xgeGamepadButtonReleased(int iGamepad, uint32_t iButton)本帧是否刚释放
float xgeGamepadAxis(int iGamepad, int iAxis)获取轴值
int xgeGamepadSetConnected(int iGamepad, int bConnected)模拟连接/断开
int xgeGamepadSetState(int iGamepad, const xge_gamepad_state_t* pState)注入虚拟状态

构建并验证本章示例

gcc -O2 -Wall -Wextra -DXGE_DLL -DXGE_DEBUGMODE=0 -I. -o build\ch119_main1.exe examples\tutorial_capture\ch119_main1.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
build\ch119_main1.exe --frames 2
build\ch119_main1.exe --capture tutorial\img\ch119_1.png

示例:手柄控制角色

void onFrame(void) {
    // 检查 1P 手柄
    if (!xgeGamepadConnected(0))
        return;

    // 左摇杆移动(带死区)
    float lx = xgeGamepadAxis(0,
        0/* 左摇杆 X */);
    float ly = xgeGamepadAxis(0,
        1/* 左摇杆 Y */);

    if (fabsf(lx) < 0.15f) lx = 0;
    if (fabsf(ly) < 0.15f) ly = 0;

    player.x += lx * 200.0f * xgeGetDelta();
    player.y += ly * 200.0f * xgeGetDelta();

    // A 键跳跃
    if (xgeGamepadButtonPressed(0,
        XGE_GAMEPAD_A)) {
        player.vy = -400.0f;
    }

    // RT 扳机射击(模拟量控制射速)
    float rt = xgeGamepadAxis(0,
        4/* 右扳机 */);
    if (rt > 0.5f) {
        fireWeapon(rt);
    }

    // 右摇杆瞄准
    float rx = xgeGamepadAxis(0,
        2/* 右摇杆 X */);
    float ry = xgeGamepadAxis(0,
        3/* 右摇杆 Y */);
    aimAngle = atan2f(ry, rx);
}

完整代码

本页旧版的按键枚举会随平台映射变化,不再作为权威实现。已验证的完整源码是 examples/tutorial_capture/ch119_main1.c:它在测试模式下调用 xgeGamepadSetState 注入状态,再用 xgeGamepadConnected、xgeGamepadButtonDown、xgeGamepadButtonPressed 与 xgeGamepadAxis 读回并断言。实际游戏应只轮询真实设备;状态注入只用于测试和编辑器工具。

将以下代码保存为 ch119_main.c,编译后运行即可看到手柄控制角色效果:

#include "xge.h"
#include <math.h>

static xge_font g_font = NULL;
static float g_px = 400.0f, g_py = 300.0f;
static float g_aimAngle = 0.0f;

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x081114FF);

    if (!xgeGamepadConnected(0)) {
        xgeTextDraw(g_font, "No gamepad connected",
            280, 280, 0xFF6B5EFF);
        return XGE_OK;
    }

    float dt = xgeGetDelta();

    // 左摇杆移动(带死区)
    float lx = xgeGamepadAxis(0, 0/* 左摇杆 X */);
    float ly = xgeGamepadAxis(0, 1/* 左摇杆 Y */);
    if (fabsf(lx) < 0.15f) lx = 0;
    if (fabsf(ly) < 0.15f) ly = 0;
    g_px += lx * 250.0f * dt;
    g_py += ly * 250.0f * dt;

    // 右摇杆瞒准
    float rx = xgeGamepadAxis(0, 2/* 右摇杆 X */);
    float ry = xgeGamepadAxis(0, 3/* 右摇杆 Y */);
    if (fabsf(rx) > 0.2f || fabsf(ry) > 0.2f)
        g_aimAngle = atan2f(ry, rx);

    // 绘制角色
    xgeShapeCircleFill(g_px, g_py, 18, 0x4FD8C2FF);
    // 瞒准方向线
    xgeShapeLine(g_px, g_py,
        g_px + 40.0f*cosf(g_aimAngle),
        g_py + 40.0f*sinf(g_aimAngle),
        0xFF6B5EFF, 3);

    // A 键跳跃反馈
    if (xgeGamepadButtonPressed(0, XGE_GAMEPAD_A))
        xgeShapeCircleFill(g_px, g_py, 30, 0xFFB454FF, 2);

    // HUD
    char buf[128];
    sprintf(buf, "LStick(%.2f,%.2f) Aim:%.1f\xc2\xb0",
        lx, ly, g_aimAngle * 57.3f);
    xgeTextDraw(g_font, buf, 30, 30, 0xFFFFFFFF);
    xgeTextDraw(g_font, "Gamepad 0 connected",
        30, 65, 0x4FD8C2FF);

    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth  = 800;
    d.iHeight = 600;
    d.sTitle  = "ch119 - Gamepad";
    d.iFlags  = XGE_INIT_VSYNC;
    xgeInit(&d);

    g_font = xgeFontLoad("res/font/default.ttf", 24);
    xgeRun(onFrame, NULL);

    xgeFontFree(g_font);
    xgeUnit();
    return 0;
}

代码讲解

  • xgeGamepadConnected:每帧检查手柄是否连接(支持热插拔)。
  • xgeGamepadAxis:获取摇杆轴值(-1~1),需手动设置死区。
  • xgeGamepadButtonPressed:仅本帧触发一次(A 键跳跃)。
  • 右摇杆瞒准:atan2 计算角度,绘制方向指示线。
  • 死区处理:摇杆物理漂移,小于 0.15 的输入忽略。

要点与陷阱

  • 死区:摇杆有物理漂移,需手动设置死区(0.1~0.2)。
  • 热插拔:每帧检查 Connected,断开时切换键盘控制。
  • SetState:可注入虚拟手柄状态(网络对战回放)。
  • Pressed vs Down:与键盘相同语义,Pressed 仅一帧。

进阶练习

  1. 实现手柄 UI 导航:DPad 切换焦点,A 确认,B 返回。
  2. 双人本地对战:同时读取两个手柄状态。
XGE gamepad state injection and polling
图:可执行示例注入左摇杆 X=0.68、Y=-0.42 与一个按下的按钮位,读回、断言并以摇杆位置和按钮灯显示结果。