Chapter 115 · 输入系统

键盘输入

KeyDown / KeyPressed / KeyRepeated / KeyReleased 四种查询模式,覆盖持续按住、首次按下、重复触发、释放检测。

115

概念

XGE 键盘输入提供四种查询语义:
• KeyDown:当前帧按键处于按下状态(持续)
• KeyPressed:本帧刚按下(仅触发一次)
• KeyRepeated:系统重复触发(如长按输入字符)
• KeyReleased:本帧刚释放
按键码使用 XGE_KEY_* 枚举(与平台无关)。

API 速查

函数说明
int xgeKeyDown(int iKey)按键是否按下(持续)
int xgeKeyPressed(int iKey)本帧是否刚按下
int xgeKeyRepeated(int iKey)是否重复触发
int xgeKeyReleased(int iKey)本帧是否刚释放
void xgeInputConsumeKey(int iKey)消费按键(阻止传递)
int xgeInputKeyConsumed(int iKey)查询是否已被消费

编译本章示例

gcc -O2 -I. -o ch115.exe ch115_main.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch115.exe

示例:角色移动 + 跳跃

void onFrame(void) {
    float speed = 200.0f * xgeGetDelta();

    // 持续按住:WASD 移动
    if (xgeKeyDown('A'))
        player.x -= speed;
    if (xgeKeyDown('D'))
        player.x += speed;
    if (xgeKeyDown('W'))
        player.y -= speed;
    if (xgeKeyDown('S'))
        player.y += speed;

    // 首次按下:空格跳跃(不重复触发)
    if (xgeKeyPressed(XGE_KEY_SPACE)) {
        player.vy = -400.0f;
    }

    // 释放检测:松开 Shift 停止加速
    if (xgeKeyReleased(XGE_KEY_LEFT_SHIFT)) {
        player.sprinting = 0;
    }

    // Esc 退出
    if (xgeKeyPressed(XGE_KEY_ESCAPE)) {
        xgeQuit();
    }
}

完整代码

将以下代码保存为 ch115_main.c,编译后运行即可看到键盘控制角色移动效果:

#include "xge.h"

static xge_font g_font = NULL;
static float g_px = 400.0f, g_py = 300.0f;
static float g_vy = 0.0f;
static int g_grounded = 1;
static int g_sprinting = 0;

static int onFrame(void* pUser) {
    (void)pUser;
    float dt = xgeGetDelta();
    float speed = g_sprinting ? 400.0f : 200.0f;

    // WASD 持续移动
    if (xgeKeyDown('A')) g_px -= speed * dt;
    if (xgeKeyDown('D')) g_px += speed * dt;
    if (xgeKeyDown('W')) g_py -= speed * dt;
    if (xgeKeyDown('S')) g_py += speed * dt;

    // Shift 加速
    g_sprinting = xgeKeyDown(XGE_KEY_LEFT_SHIFT);

    // 空格跳跃(仅触发一次)
    if (xgeKeyPressed(XGE_KEY_SPACE) && g_grounded) {
        g_vy = -350.0f;
        g_grounded = 0;
    }

    // 简单重力
    g_vy += 800.0f * dt;
    g_py += g_vy * dt;
    if (g_py > 500.0f) {
        g_py = 500.0f;
        g_vy = 0;
        g_grounded = 1;
    }

    // Esc 退出
    if (xgeKeyPressed(XGE_KEY_ESCAPE))
        xgeQuit();

    // 绘制
    xgeClear(0x081114FF);
    xgeShapeRectFill((xge_rect_t){0,520,800,80}, 0x224422FF);
    xgeShapeRectFill(
        (xge_rect_t){g_px-15, g_py-30, 30, 30},
        g_sprinting ? 0xFFB454FF : 0x4FD8C2FF);

    char buf[128];
    sprintf(buf, "WASD:move Shift:sprint Space:jump Esc:quit");
    xgeTextDraw(g_font, buf, 50, 30, 0xFFFFFFFF);

    return XGE_OK;
}

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

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

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

代码讲解

  • xgeKeyDown:持续按住检测,用于 WASD 移动(每帧都响应)。
  • xgeKeyPressed:仅本帧触发一次,用于跳跃/退出等一次性操作。
  • xgeKeyReleased:释放检测(本章示例用 Shift 持续按住代替)。
  • xgeGetDelta:帧间隔时间,保证不同帧率下移动速度一致。
  • 重力模拟:vy 累加速度,落地后重置。

要点与陷阱

  • Pressed vs Down:Pressed 仅一帧为真;Down 持续为真。
  • Repeated:用于文本输入场景(长按连续输入字符)。
  • ConsumeKey:UI 层消费按键后,游戏层不再响应。
  • 帧同步:所有查询基于当前帧快照,帧间状态由引擎维护。

进阶练习

  1. 实现按键绑定系统:可配置键位映射。
  2. 组合键检测:Ctrl+S 保存、Ctrl+Z 撤销。

已验证实现

权威源码为 examples/tutorial_capture/ch115_main1.c。它通过公开的 xgeInputEventPost 回放 KEY_DOWN 与 KEY_UP,再以 xgeInputEventGet 顺序消费并断言最终动作状态已释放。游戏代码应将这些事件映射到自己的 action 状态,而不是把平台按键散落在玩法逻辑中。

XGE keyboard ordered event replay screenshot
图:键盘按下与抬起事件的真实回放。绿色 PASS 表示两个事件按顺序消费,空格键最终回到释放状态。