SDL3_API分类参考_按键码(CategoryKeycode)

2026-3-6 / 0 评论 / 5 阅读

按键码子系统(CategoryKeycode)

该模块定义了用于标识键盘按键和修饰键的核心常量,是 SDL 键盘输入处理的基础数据类型定义模块。


函数

  • (无)

数据类型

  • SDL_Keycode:按键码类型(用于标识键盘上的「符号键」,如 SDLK_a 代表字母 A、SDLK_ESCAPE 代表 ESC 键,与键盘布局相关)
  • SDL_Keymod:修饰键状态类型(用于标识 Shift/Ctrl/Alt/GUI 等修饰键的按下状态,可通过位运算组合多个修饰键)

结构体

  • (无)

枚举

  • (无)

  • (无)

FreeBASIC 示例代码

' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"

' 辅助函数:解析修饰键状态并返回描述字符串
Function GetModStateString(ByVal modState As SDL_Keymod) As String
    Dim As String modStr = ""

    ' 位运算判断修饰键状态(支持多键组合)
    If (modState And SDL_MOD_SHIFT) Then modStr &= "Shift "
    If (modState And SDL_MOD_CTRL) Then modStr &= "Ctrl "
    If (modState And SDL_MOD_ALT) Then modStr &= "Alt "
    If (modState And SDL_MOD_GUI) Then modStr &= "GUI "  ' Windows/Command 键
    If (modState And SDL_MOD_CAPS) Then modStr &= "CapsLock "
    If (modState And SDL_MOD_NUM) Then modStr &= "NumLock "

    If (modStr = "") Then modStr = "无修饰键"
    Return modStr
End Function

' 辅助函数:打印按键码相关信息
Sub PrintKeycodeInfo(ByVal keycode As SDL_Keycode)
    Dim As ZString Ptr keyName = SDL_GetKeyName(keycode)
    Dim As SDL_Scancode scancode = SDL_GetScancodeFromKey(keycode)
    Dim As ZString Ptr scName = SDL_GetScancodeName(scancode)

    SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
        "按键码:0x%X | 按键名称:%s | 对应扫描码:0x%X | 扫描码名称:%s", _
        keycode, keyName, scancode, scName)

    SDL_free(keyName)
    SDL_free(scName)
End Sub

' 主程序
Dim As SDL_Window Ptr window = NULL
Dim As SDL_Event evt
Dim As Boolean quit = False

' 1. 初始化 SDL
If (SDL_Init(SDL_INIT_VIDEO) < 0) Then
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
    End 1
End If

' 2. 创建窗口
window = SDL_CreateWindow("SDL Keycode 示例", _
    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _
    600, 400, SDL_WINDOW_SHOWN)
If (window = NULL) Then
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建窗口失败:%s", SDL_GetError())
    SDL_Quit()
    End 1
End If

SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL_Keycode / SDL_Keymod 示例 ===")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按任意键查看按键码信息 | 按 F1 查看当前修饰键状态 | 按 ESC 退出")

' 3. 主事件循环
While (Not quit)
    While (SDL_PollEvent(@evt))
        Select Case evt.type
            ' 窗口关闭事件
            Case SDL_QUITEVENT
                quit = True

            ' 键盘按键事件
            Case SDL_EVENT_KEY_DOWN
                Select Case evt.key.key
                    Case SDLK_ESCAPE
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下 ESC 键,退出程序")
                        quit = True

                    Case SDLK_F1
                        ' 获取并打印当前修饰键状态
                        Dim As SDL_Keymod modState = SDL_GetModState()
                        SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "当前修饰键状态:%s", GetModStateString(modState))

                    Case Else
                        ' 打印按键码详细信息
                        SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 按键信息 ====")
                        PrintKeycodeInfo(evt.key.key)

                        ' 打印该按键的修饰键状态
                        SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "按下时的修饰键:%s", GetModStateString(evt.key.mod))
                End Select

            ' 手动设置修饰键示例(按 F2 模拟按下 Shift+Ctrl)
            Case SDL_EVENT_KEY_UP
                If (evt.key.key = SDLK_F2) Then
                    Dim As SDL_Keymod newModState = SDL_MOD_SHIFT Or SDL_MOD_CTRL
                    SDL_SetModState(newModState)
                    SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "手动设置修饰键状态为:%s", GetModStateString(newModState))
                End If
        End Select
    Wend

    SDL_Delay(10)
Wend

' 4. 清理资源
SDL_DestroyWindow(window)
SDL_Quit()

SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出")
End 0

核心常量补充说明

在实际使用中,SDL 预定义了大量常用的 SDL_Keycode 常量,以下是最常用的分类:

类别 示例常量 说明
字母键 SDLK_a, SDLK_b, SDLK_Z 大小写统一用大写字母标识
数字键 SDLK_0, SDLK_1, SDLK_9 主键盘数字键
功能键 SDLK_F1, SDLK_F2, SDLK_F12 功能键 F1-F12
控制键 SDLK_ESCAPE, SDLK_RETURN, SDLK_TAB 常用控制键
方向键 SDLK_UP, SDLK_DOWN, SDLK_LEFT, SDLK_RIGHT 方向键
修饰键 无(修饰键用 SDL_Keymod 表示) Shift/Ctrl/Alt 等

SDL_Keymod 常用常量:

  • SDL_MOD_SHIFT:Shift 键(左/右 Shift 均适用)
  • SDL_MOD_CTRL:Ctrl 键(左/右 Ctrl 均适用)
  • SDL_MOD_ALT:Alt 键(左/右 Alt 均适用)
  • SDL_MOD_GUI:GUI 键(Windows 键/苹果 Command 键)
  • SDL_MOD_CAPS:CapsLock 锁定状态
  • SDL_MOD_NUM:NumLock 锁定状态

总结

  1. SDL_Keycode 是标识「按键符号」的核心类型,与键盘布局绑定(如 SDLK_a 始终代表字母 A,无论键盘布局如何);
  2. SDL_Keymod 是标识修饰键状态的类型,支持位运算组合(如 SDL_MOD_SHIFT Or SDL_MOD_CTRL 表示同时按下 Shift+Ctrl);
  3. 该模块无独立函数,仅定义基础数据类型和常量,需配合键盘子系统(CategoryKeyboard)的函数使用(如 SDL_GetKeyNameSDL_GetModState)。

SDL3 键盘使用最佳实践

核心前提

键盘输入看似简单,但一旦超出你常用的语言/地区范围(甚至在同一地区),就会变得异常复杂。

几乎所有游戏,无论采用何种输入方案,都应该提供按键绑定配置功能

  • 这不仅能解决不同键盘布局的适配问题;
  • 还能让用户将按键调整到最舒适的位置(尤其对行动不便的用户)。
    即使不做可视化配置界面,至少也要提供配置文件——这会让很多用户感激不尽!

四种核心使用场景

SDL 处理键盘输入主要有四种常见方式,应用可能在不同场景下混用不同方式。

场景1:「101键游戏手柄」模式

很多游戏把键盘当作「超多按键的手柄」,而非文本输入工具。典型例子是 FPS 游戏的「WASD」移动:

  • 你关心的是物理按键位置,而非按键上印的符号;
  • 比如法语键盘对应「ZQSD」、日语平假名键盘对应「てちとし」,但都是键盘上相同的物理位置。

这种场景下应使用 SDL_Scancode(扫描码)

  • 扫描码绑定物理按键位置,与按键上的符号无关;
  • 底层默认参考美式 QWERTY 键盘布局,这恰好符合「只关心位置」的需求。

事件(Events) vs 状态(States)

处理键盘/鼠标/手柄按键时,开发者常先想到事件,但两者的选择会直接影响体验:

概念区别

  • 事件(Events):系统/SDL 通知你「发生了某件事」(如按键按下/释放),不一定需要立即响应;
    • 适合「单次动作」(如跳跃、换弹);
    • 事件不会每帧触发,即使有按键重复,也会有延迟。
  • 状态(States):代表「事件处理完成后按键的最新状态」;
    • 适合「持续动作」(如移动);
    • 可在每一帧查询,避免事件重复的延迟问题。

按键重复(Key Repeat)说明

  • 按键事件不会每帧发送,SDL 也无法感知你的帧循环时机;
  • 系统会触发「按键重复事件」:首次重复延迟约1秒,之后每200ms一次(具体值由系统/用户设置决定);
  • 用事件处理移动会导致「卡顿」(类似文本编辑器按住按键的字符输入间隔)。

实战示例

状态查询(适合持续动作)

通过 SDL_GetKeyboardState 获取按键状态数组(索引为扫描码),SDL 管理数组生命周期,建议缓存以减少调用次数。

' 功能:返回玩家移动方向(1=前进,-1=后退,0=不动)
Function GetMoveDirection() As Integer
    Dim As UByte Ptr keyStates = SDL_GetKeyboardState(NULL)
    Dim As Integer direction = 0

    ' 同时按下前进/后退键时,方向抵消(符合直觉)
    If (keyStates[SDL_SCANCODE_W] = 1) Then
        direction += 1  ' 美式键盘 W 键位置:前进
    End If

    If (keyStates[SDL_SCANCODE_S] = 1) Then
        direction -= 1  ' 美式键盘 S 键位置:后退
    End If

    Return direction
End Function

事件处理(适合单次动作)

SDL_EVENT_KEY_DOWN/UP 事件中提取 scancode 字段,触发单次动作。

Enum Action
    ACTION_NONE
    ACTION_RELOAD
    ACTION_JUMP
End Enum

' 功能:根据按键事件返回玩家要执行的动作
Function GetPlayerAction(ByRef evt As SDL_Event) As Action
    If (evt.type <> SDL_EVENT_KEY_DOWN) Then
        Return ACTION_NONE
    End If

    Select Case evt.key.scancode
        Case SDL_SCANCODE_R
            Return ACTION_RELOAD  ' R 键位置:换弹
        Case SDL_SCANCODE_SPACE
            Return ACTION_JUMP    ' 空格键位置:跳跃
        Case Else
            Return ACTION_NONE
    End Select
End Function

场景2:「特定按键」模式

部分游戏需要检测「按键上的符号」,而非物理位置:

  • 比如「按 I 打开背包」—— 不管 I 键在键盘的哪个位置;
  • 也包括 ESC 取消、Enter 确认等通用按键。

(但仍建议提供按键绑定!有些键盘可能没有 I 键。)

这种场景下应使用 SDL_Keycode(按键码)

  • 按键码绑定「符号」,与物理位置无关;
  • 同样从 SDL_EVENT_KEY_DOWN/UP 事件中获取。
' 功能:循环等待用户按下 ESC 键退出
Dim As Boolean quitApp = False
Dim As SDL_Event evt

While (Not quitApp)
    While (SDL_PollEvent(@evt))
        If (evt.type = SDL_EVENT_KEY_DOWN) Then
            If (evt.key.key = SDLK_ESCAPE) Then
                quitApp = True
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下 ESC,退出程序")
            End If
        End If
    Wend
    SDL_Delay(10)
Wend

场景3:「聊天框/文本输入」模式

Unicode 处理极其复杂!如果需要输入文本(如角色命名、聊天框),绝对不要用按键事件——这无法适配全球不同的键盘和语言。

正确做法:

  1. 调用 SDL_StartTextInput 启用文本输入;
  2. 监听 SDL_EVENT_TEXT_INPUT 事件(系统会返回 UTF-8 编码的 Unicode 字符串);
  3. 输入完成后调用 SDL_StopTextInput

这种方式的优势:

  • 适配系统原生输入方式(如移动平台弹出虚拟键盘、多语言输入法候选词);
  • 你无法自己实现覆盖全球所有语言的输入方案,不要尝试基于按键事件造轮子。

缺点:虚拟键盘可能遮挡游戏界面,需要提前设计布局。

' 功能:实现简单的文本输入框
Dim As Boolean inputComplete = False
Dim As String inputText = ""
Dim As SDL_Rect inputArea = Type<SDL_Rect>(100, 100, 400, 50)  ' 输入区域
Dim As Integer cursorPos = 0
Dim As SDL_Event evt
Dim As SDL_Window Ptr window = SDL_CreateWindow("文本输入示例", _
    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _
    600, 400, SDL_WINDOW_SHOWN)

' 设置文本输入区域并启用输入
SDL_SetTextInputArea(window, @inputArea, cursorPos)
SDL_StartTextInput(window)

While (Not inputComplete)
    While (SDL_PollEvent(@evt))
        Select Case evt.type
            Case SDL_EVENT_KEY_DOWN
                ' ESC 取消 / Enter 确认
                If (evt.key.key = SDLK_ESCAPE Or evt.key.key = SDLK_RETURN) Then
                    SDL_StopTextInput(window)
                    inputComplete = True
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "输入完成:%s", inputText)
                End If

            Case SDL_EVENT_TEXT_INPUT
                ' 追加输入的文本(UTF-8 编码)
                inputText &= *evt.text.text
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "当前输入:%s", inputText)
        End Select
    Wend

    ' 更新输入区域(适配输入法候选词位置)
    SDL_SetTextInputArea(window, @inputArea, cursorPos)
    SDL_Delay(10)
Wend

SDL_DestroyWindow(window)

全屏游戏如需自定义输入法UI,可设置 SDL_HINT_IME_IMPLEMENTED_UI 提示,并处理 SDL_EVENT_TEXT_EDITINGSDL_EVENT_TEXT_EDITING_CANDIDATES 事件。参考示例:testime.c

场景4:「文本编辑器」模式

这是特殊场景,非必要不要使用——多数时候你只是想「错误地从零实现聊天框」。

如果要做 SDL 版 Vim 这类工具,需要处理:

  • 修饰键组合(如 Shift+Z 两次);
  • 小键盘方向键(NumLock 切换数字/方向);
  • 按键事件与文本输入事件的关联。

此时需要同时处理按键事件和输入事件,并通过 SDL_GetKeyFromScancode 获取带修饰键的按键码:

' 功能:检测按下的 $ 键(美式键盘 Shift+4 / 法语键盘直接按 $)
Dim As Boolean quitApp = False
Dim As SDL_Event evt

While (Not quitApp)
    While (SDL_PollEvent(@evt))
        If (evt.type = SDL_EVENT_KEY_DOWN) Then
            ' 获取带修饰键的按键码
            Dim As SDL_Keycode keycode = SDL_GetKeyFromScancode(evt.key.scancode, evt.key.mod, False)
            If (keycode = Asc("$")) Then
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下了 $ 键!")
            End If

            ' ESC 退出
            If (evt.key.key = SDLK_ESCAPE) Then
                quitApp = True
            End If
        End If
    Wend
    SDL_Delay(10)
Wend

注意:很多按键不生成字符,部分字符需要多键组合/输入法生成,这是小众需求,不适合通用输入场景。

向用户展示按键名称

实现自定义按键绑定时,需要在UI中显示按键名称(如「按 [空格] 跳跃」),应使用 SDL_GetKeyName 配合按键码:

' 功能:打印用户按下的按键名称
Dim As Boolean quitApp = False
Dim As SDL_Event evt

While (Not quitApp)
    While (SDL_PollEvent(@evt))
        If (evt.type = SDL_EVENT_KEY_DOWN) Then
            Dim As ZString Ptr keyName = SDL_GetKeyName(evt.key.key)
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "你按下了 %s 键", keyName)
            SDL_free(keyName)  ' 释放字符串

            ' ESC 退出
            If (evt.key.key = SDLK_ESCAPE) Then
                quitApp = True
            End If
        End If
    Wend
    SDL_Delay(10)
Wend

注意:SDL_GetKeyName 仅返回大写字符,这适合向用户展示「按这个符号的按键」。


完整示例(整合所有场景)

#Include "SDL.bi"

Enum PlayerAction
    ACTION_NONE
    ACTION_JUMP
    ACTION_RELOAD
End Enum

' 获取移动方向(状态查询)
Function GetMoveDirection() As Integer
    Dim As UByte Ptr keyStates = SDL_GetKeyboardState(NULL)
    Dim As Integer dir = 0
    If (keyStates[SDL_SCANCODE_W]) Then dir += 1
    If (keyStates[SDL_SCANCODE_S]) Then dir -= 1
    Return dir
End Function

' 获取玩家动作(事件处理)
Function GetPlayerAction(ByRef evt As SDL_Event) As PlayerAction
    If (evt.type <> SDL_EVENT_KEY_DOWN) Then Return ACTION_NONE
    Select Case evt.key.scancode
        Case SDL_SCANCODE_SPACE: Return ACTION_JUMP
        Case SDL_SCANCODE_R: Return ACTION_RELOAD
        Case Else: Return ACTION_NONE
    End Select
End Function

' 主程序
Dim As SDL_Window Ptr window = NULL
Dim As SDL_Event evt
Dim As Boolean quit = False, textInputActive = False
Dim As String chatText = ""

' 初始化 SDL
If (SDL_Init(SDL_INIT_VIDEO) < 0) Then
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
    End 1
End If

' 创建窗口
window = SDL_CreateWindow("SDL 键盘最佳实践示例", _
    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _
    800, 600, SDL_WINDOW_SHOWN)
If (window = NULL) Then
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建窗口失败:%s", SDL_GetError())
    SDL_Quit()
    End 1
End If

SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== 操作说明 ===")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "W/S:移动(状态查询)| 空格:跳跃/R:换弹(事件)| T:打开聊天框 | ESC:退出")

' 主循环
While (Not quit)
    ' 处理移动(每帧查询状态)
    Dim As Integer moveDir = GetMoveDirection()
    If (moveDir <> 0) Then
        Static As Integer lastDir = 0
        If (moveDir <> lastDir) Then
            SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "移动方向:%s", IIf(moveDir=1, "前进", "后退"))
            lastDir = moveDir
        End If
    End If

    ' 处理事件
    While (SDL_PollEvent(@evt))
        Select Case evt.type
            Case SDL_QUITEVENT
                quit = True

            Case SDL_EVENT_KEY_DOWN
                ' ESC 退出
                If (evt.key.key = SDLK_ESCAPE) Then
                    quit = True
                End If

                ' T 键打开聊天框
                If (evt.key.key = SDLK_t And Not textInputActive) Then
                    textInputActive = True
                    chatText = ""
                    SDL_StartTextInput(window)
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "聊天框已打开,输入文本后按 Enter 发送")
                End If

                ' 处理玩家动作
                Dim As PlayerAction action = GetPlayerAction(evt)
                Select Case action
                    Case ACTION_JUMP: SDL_LogInfo(SDL_LOG_CATEGORY_GAME, "玩家跳跃")
                    Case ACTION_RELOAD: SDL_LogInfo(SDL_LOG_CATEGORY_GAME, "玩家换弹")
                End Select

            Case SDL_EVENT_TEXT_INPUT
                If (textInputActive) Then
                    chatText &= *evt.text.text
                    SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "聊天输入:%s", chatText)
                End If

            Case SDL_EVENT_KEY_UP
                ' Enter 发送聊天
                If (evt.key.key = SDLK_RETURN And textInputActive) Then
                    SDL_StopTextInput(window)
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "发送聊天:%s", chatText)
                    textInputActive = False
                End If
        End Select
    Wend

    SDL_Delay(10)
Wend

' 清理资源
SDL_DestroyWindow(window)
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出")
End 0

总结

  1. 核心原则:始终提供按键绑定配置,适配不同键盘和用户需求;
  2. 场景选择
    • 物理位置(移动)→ 扫描码 + 状态查询;
    • 按键符号(ESC/Enter)→ 按键码 + 事件;
    • 文本输入(聊天/命名)→ SDL_StartTextInput + 文本输入事件;
    • 复杂编辑器 → 混合按键/输入事件 + 修饰键处理;
  3. 关键避坑:不要基于按键事件实现多语言文本输入,依赖系统原生输入法接口。

评论一下?

OωO
取消