摇杆子系统(CategoryJoystick)
SDL 提供的底层摇杆设备处理模块,适用于需要精细控制摇杆硬件的场景。如果只需简单的、按键功能标准化的控制器交互,建议使用游戏手柄(Gamepad)API 而非本模块。
核心概念说明
- instance_id(实例ID):系统中摇杆设备的当前实例标识。摇杆拔插后会生成新的 instance_id,该ID是单调递增的数值。
- player_index(玩家索引):分配给特定控制器的玩家编号(XInput 控制器会返回 XInput 用户索引,多数普通摇杆无法提供此信息)。
- SDL_GUID:摇杆设备的稳定 128 位唯一标识,不会随时间/拔插变化,用于识别设备类型(如 Xbox 360 有线手柄),该标识与平台相关。
基础使用前提
- 调用
SDL_Init()时必须传入SDL_INIT_JOYSTICK标志,SDL 会扫描系统摇杆设备并加载对应驱动; - 若需应用在后台时仍接收摇杆事件,需设置
SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS提示。
虚拟摇杆功能
SDL 支持创建虚拟摇杆:
- 通过
SDL_AttachVirtualJoystick()定义虚拟控制器; - 调用
SDL_SetJoystickVirtualAxis()/SDL_SetJoystickVirtualButton()等接口模拟输入; - 虚拟摇杆对 SDL 而言与物理摇杆无差异(无硬件驱动支撑),可用于适配 VR 控制器、实现输入录制/回放等场景。
函数
- SDL_AttachVirtualJoystick:创建并附加虚拟摇杆(传入设备描述结构体,返回虚拟摇杆ID)
- SDL_CloseJoystick:关闭已打开的摇杆设备(释放摇杆资源)
- SDL_DetachVirtualJoystick:分离并销毁虚拟摇杆
- SDL_GetJoystickAxis:获取摇杆指定轴的当前值(范围:
SDL_JOYSTICK_AXIS_MIN至SDL_JOYSTICK_AXIS_MAX) - SDL_GetJoystickAxisInitialState:获取摇杆轴的初始状态(设备刚连接时的轴值)
- SDL_GetJoystickBall:获取摇杆轨迹球的相对移动量(老式摇杆设备的轨迹球)
- SDL_GetJoystickButton:获取摇杆指定按键的当前状态(1=按下,0=释放)
- SDL_GetJoystickConnectionState:获取摇杆的连接状态(如已连接、断开、正在连接)
- SDL_GetJoystickFirmwareVersion:获取摇杆的固件版本号
- SDL_GetJoystickFromID:通过摇杆ID获取对应的 SDL_Joystick 指针
- SDL_GetJoystickFromPlayerIndex:通过玩家索引获取对应的 SDL_Joystick 指针
- SDL_GetJoystickGUID:获取已打开摇杆的 GUID 标识
- SDL_GetJoystickGUIDForID:通过摇杆ID获取对应的 GUID 标识
- SDL_GetJoystickGUIDInfo:解析 GUID 信息(提取厂商/产品ID等)
- SDL_GetJoystickHat:获取摇杆方向键(Hat)的当前状态(如上、下、左、右、组合方向)
- SDL_GetJoystickID:获取已打开摇杆的实例ID
- SDL_GetJoystickName:获取已打开摇杆的设备名称
- SDL_GetJoystickNameForID:通过摇杆ID获取设备名称
- SDL_GetJoystickPath:获取已打开摇杆的系统路径(如 /dev/input/js0)
- SDL_GetJoystickPathForID:通过摇杆ID获取设备系统路径
- SDL_GetJoystickPlayerIndex:获取摇杆的玩家索引
- SDL_GetJoystickPlayerIndexForID:通过摇杆ID获取玩家索引
- SDL_GetJoystickPowerInfo:获取摇杆的电源信息(如供电方式、电量)
- SDL_GetJoystickProduct:获取摇杆的产品ID(PID)
- SDL_GetJoystickProductForID:通过摇杆ID获取产品ID
- SDL_GetJoystickProductVersion:获取摇杆的产品版本号
- SDL_GetJoystickProductVersionForID:通过摇杆ID获取产品版本号
- SDL_GetJoystickProperties:获取摇杆的属性集合(自定义设备属性)
- SDL_GetJoysticks:枚举系统中所有已连接的摇杆设备(返回ID列表及数量)
- SDL_GetJoystickSerial:获取摇杆的序列号(部分设备支持)
- SDL_GetJoystickType:获取摇杆的设备类型(如普通摇杆、PS手柄、Xbox手柄)
- SDL_GetJoystickTypeForID:通过摇杆ID获取设备类型
- SDL_GetJoystickVendor:获取摇杆的厂商ID(VID)
- SDL_GetJoystickVendorForID:通过摇杆ID获取厂商ID
- SDL_GetNumJoystickAxes:获取摇杆的轴数量(如左摇杆X/Y轴、右摇杆X/Y轴)
- SDL_GetNumJoystickBalls:获取摇杆的轨迹球数量
- SDL_GetNumJoystickButtons:获取摇杆的按键数量
- SDL_GetNumJoystickHats:获取摇杆的方向键(Hat)数量
- SDL_HasJoystick:检查系统是否连接了至少一个摇杆设备(返回布尔值)
- SDL_IsJoystickVirtual:检查指定摇杆是否为虚拟摇杆(返回布尔值)
- SDL_JoystickConnected:检查指定摇杆是否处于已连接状态
- SDL_JoystickEventsEnabled:检查摇杆事件是否启用(返回布尔值)
- SDL_LockJoysticks:锁定摇杆子系统(多线程访问时防止竞争)
- SDL_OpenJoystick:打开指定ID的摇杆设备(返回 SDL_Joystick 指针,用于后续操作)
- SDL_RumbleJoystick:启用摇杆的全局震动(设置左右电机的强度和持续时间)
- SDL_RumbleJoystickTriggers:启用摇杆扳机键的震动(仅部分设备支持)
- SDL_SendJoystickEffect:发送自定义震动效果到摇杆(如特定的震动模式)
- SDL_SendJoystickVirtualSensorData:向虚拟摇杆发送传感器数据(如陀螺仪、加速度计)
- SDL_SetJoystickEventsEnabled:启用/禁用摇杆事件(禁用后不再接收摇杆相关事件)
- SDL_SetJoystickLED:设置摇杆的LED灯颜色(部分设备支持)
- SDL_SetJoystickPlayerIndex:设置摇杆的玩家索引
- SDL_SetJoystickVirtualAxis:设置虚拟摇杆指定轴的数值
- SDL_SetJoystickVirtualBall:设置虚拟摇杆轨迹球的相对移动量
- SDL_SetJoystickVirtualButton:设置虚拟摇杆指定按键的状态
- SDL_SetJoystickVirtualHat:设置虚拟摇杆方向键的状态
- SDL_SetJoystickVirtualTouchpad:设置虚拟摇杆触摸板的输入数据
- SDL_TryLockJoysticks:尝试锁定摇杆子系统(非阻塞,失败返回false)
- SDL_UnlockJoysticks:解锁摇杆子系统
- SDL_UpdateJoysticks:强制更新所有摇杆的状态(SDL 内部自动调用,一般无需手动调用)
数据类型
- SDL_Joystick:摇杆设备句柄类型(标识已打开的摇杆设备)
- SDL_JoystickID:摇杆实例ID类型(系统中摇杆设备的唯一实例标识)
结构体
- SDL_VirtualJoystickDesc:虚拟摇杆描述结构体(定义虚拟摇杆的轴、按键、方向键数量等)
- SDL_VirtualJoystickSensorDesc:虚拟摇杆传感器描述结构体(定义支持的传感器类型)
- SDL_VirtualJoystickTouchpadDesc:虚拟摇杆触摸板描述结构体(定义触摸板参数)
枚举
- SDL_JoystickConnectionState:摇杆连接状态枚举(SDL_JOYSTICK_DISCONNECTED 断开、SDL_JOYSTICK_CONNECTED 已连接等)
- SDL_JoystickType:摇杆设备类型枚举(SDL_JOYSTICK_TYPE_UNKNOWN 未知、SDL_JOYSTICK_TYPE_XBOX360 Xbox360手柄等)
宏
- SDL_JOYSTICK_AXIS_MAX:摇杆轴的最大值(通常为 32767)
- SDL_JOYSTICK_AXIS_MIN:摇杆轴的最小值(通常为 -32768)
FreeBASIC 示例代码
' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"
' 辅助函数:打印摇杆设备信息
Sub PrintJoystickInfo(ByVal joystick As SDL_Joystick Ptr)
If (joystick = NULL) Then Return
' 基础信息
Dim As ZString Ptr name = SDL_GetJoystickName(joystick)
Dim As SDL_JoystickID joyID = SDL_GetJoystickID(joystick)
Dim As SDL_JoystickType joyType = SDL_GetJoystickType(joystick)
' 硬件信息
Dim As Uint16 vendor = SDL_GetJoystickVendor(joystick)
Dim As Uint16 product = SDL_GetJoystickProduct(joystick)
Dim As Uint16 productVer = SDL_GetJoystickProductVersion(joystick)
' 功能数量
Dim As Integer numAxes = SDL_GetNumJoystickAxes(joystick)
Dim As Integer numButtons = SDL_GetNumJoystickButtons(joystick)
Dim As Integer numHats = SDL_GetNumJoystickHats(joystick)
Dim As Integer numBalls = SDL_GetNumJoystickBalls(joystick)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 摇杆设备信息 ====")
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "ID:%d | 名称:%s | 类型:%d", joyID, name, joyType)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "厂商ID:0x%X | 产品ID:0x%X | 版本:%d", vendor, product, productVer)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "轴数量:%d | 按键数量:%d | 方向键数量:%d | 轨迹球数量:%d", _
numAxes, numButtons, numHats, numBalls)
If (name <> NULL) Then SDL_free(name)
End Sub
' 辅助函数:打印摇杆轴状态
Sub PrintJoystickAxisState(ByVal joystick As SDL_Joystick Ptr)
If (joystick = NULL) Then Return
Dim As Integer numAxes = SDL_GetNumJoystickAxes(joystick)
If (numAxes = 0) Then Return
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 摇杆轴状态 ====")
For i As Integer = 0 To numAxes - 1
Dim As Sint16 axisValue = SDL_GetJoystickAxis(joystick, i)
' 归一化轴值(-1.0 ~ 1.0)
Dim As Double normalized = axisValue / 32767.0
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "轴 %d:%d (%.2f)", i, axisValue, normalized)
Next
End Sub
' 主程序
Dim As SDL_Joystick Ptr joystick = NULL
Dim As SDL_JoystickID Ptr joyIDs = NULL
Dim As Integer joyCount = 0
Dim As SDL_Event evt
Dim As Boolean quit = False
' 1. 初始化 SDL(必须包含 JOYSTICK 标志)
If (SDL_Init(SDL_INIT_VIDEO Or SDL_INIT_JOYSTICK) < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
End 1
End If
' 2. 检查摇杆设备
If (Not SDL_HasJoystick()) Then
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "系统未检测到摇杆设备,将创建虚拟摇杆演示")
' 创建虚拟摇杆
Dim As SDL_VirtualJoystickDesc virtDesc
SDL_memset(@virtDesc, 0, SizeOf(SDL_VirtualJoystickDesc))
virtDesc.type = SDL_JOYSTICK_TYPE_GAMECONTROLLER
virtDesc.naxes = 4 ' 4个轴(左摇杆X/Y,右摇杆X/Y)
virtDesc.nbuttons = 16 ' 16个按键
virtDesc.nhats = 1 ' 1个方向键
virtDesc.name = "Virtual Gamepad"
Dim As SDL_JoystickID virtID = SDL_AttachVirtualJoystick(@virtDesc)
If (virtID = SDL_INVALID_JOYSTICK_ID) Then
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "创建虚拟摇杆失败:%s", SDL_GetError())
SDL_Quit()
End 1
End If
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "创建虚拟摇杆成功,ID:%d", virtID)
End If
' 枚举所有摇杆设备
joyCount = SDL_GetJoysticks(0, NULL)
If (joyCount > 0) Then
joyIDs = Callocate(joyCount * SizeOf(SDL_JoystickID))
joyCount = SDL_GetJoysticks(joyCount, joyIDs)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "检测到 %d 个摇杆设备", joyCount)
' 打开第一个摇杆设备
joystick = SDL_OpenJoystick(joyIDs[0])
If (joystick = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "打开摇杆失败:%s", SDL_GetError())
Else
PrintJoystickInfo(joystick)
' 启用摇杆事件
SDL_SetJoystickEventsEnabled(SDL_TRUE)
End If
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL 摇杆示例 ===")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "摇杆操作:移动轴/按下按键/方向键 | F1:打印轴状态 | F2:触发震动 | 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.scancode
Case SDL_SCANCODE_ESCAPE
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下 ESC,退出程序")
quit = True
' F1:打印轴状态
Case SDL_SCANCODE_F1
If (joystick <> NULL) Then
PrintJoystickAxisState(joystick)
End If
' F2:触发摇杆震动(仅物理设备支持)
Case SDL_SCANCODE_F2
If (joystick <> NULL And Not SDL_IsJoystickVirtual(joystick)) Then
' 左电机(低频)强度 0.5,右电机(高频)强度 0.8,持续 1000ms
If (SDL_RumbleJoystick(joystick, 0.5 * 0xFFFF, 0.8 * 0xFFFF, 1000) = 0) Then
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "触发摇杆震动 1 秒")
Else
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "该摇杆不支持震动")
End If
End If
End Select
' 摇杆设备插拔事件
Case SDL_EVENT_JOYSTICK_ADDED
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "摇杆设备已连接,ID:%d", evt.jdevice.which)
' 自动打开新连接的摇杆
If (joystick = NULL) Then
joystick = SDL_OpenJoystick(evt.jdevice.which)
If (joystick <> NULL) Then
PrintJoystickInfo(joystick)
End If
End If
Case SDL_EVENT_JOYSTICK_REMOVED
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "摇杆设备已断开,ID:%d", evt.jdevice.which)
If (joystick <> NULL And SDL_GetJoystickID(joystick) = evt.jdevice.which) Then
SDL_CloseJoystick(joystick)
joystick = NULL
End If
' 摇杆轴移动事件
Case SDL_EVENT_JOYSTICK_AXIS_MOTION
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "摇杆轴 %d 移动:%d (设备ID:%d)", _
evt.jaxis.axis, evt.jaxis.value, evt.jaxis.which)
' 摇杆按键事件
Case SDL_EVENT_JOYSTICK_BUTTON_DOWN
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "摇杆按键 %d 按下(设备ID:%d)", _
evt.jbutton.button, evt.jbutton.which)
Case SDL_EVENT_JOYSTICK_BUTTON_UP
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "摇杆按键 %d 释放(设备ID:%d)", _
evt.jbutton.button, evt.jbutton.which)
' 摇杆方向键事件
Case SDL_EVENT_JOYSTICK_HAT_MOTION
Dim As String hatState = ""
If (evt.jhat.value And SDL_HAT_UP) Then hatState &= "上 "
If (evt.jhat.value And SDL_HAT_DOWN) Then hatState &= "下 "
If (evt.jhat.value And SDL_HAT_LEFT) Then hatState &= "左 "
If (evt.jhat.value And SDL_HAT_RIGHT) Then hatState &= "右 "
If (hatState = "") Then hatState = "居中"
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "摇杆方向键 %d:%s(设备ID:%d)", _
evt.jhat.hat, hatState, evt.jhat.which)
End Select
Wend
' 模拟虚拟摇杆输入(每帧更新)
If (joyCount > 0 And SDL_IsJoystickVirtual(joyIDs[0])) Then
Static As Integer frame = 0
frame += 1
' 模拟左摇杆X轴左右摆动
Dim As Sint16 axisValue = Sin(frame * 0.1) * 32767
SDL_SetJoystickVirtualAxis(joyIDs[0], 0, axisValue)
' 每 100 帧模拟按键按下/释放
If (frame Mod 100 = 0) Then
Static As Boolean btnPressed = False
btnPressed = Not btnPressed
SDL_SetJoystickVirtualButton(joyIDs[0], 0, btnPressed)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "虚拟摇杆按键 0:%s", IIf(btnPressed, "按下", "释放"))
End If
End If
SDL_Delay(10)
Wend
' 4. 清理资源
If (joystick <> NULL) Then
SDL_CloseJoystick(joystick)
End If
If (joyIDs <> NULL) Then
' 销毁虚拟摇杆
For i As Integer = 0 To joyCount - 1
If (SDL_IsJoystickVirtual(joyIDs[i])) Then
SDL_DetachVirtualJoystick(joyIDs[i])
End If
Next
Deallocate(joyIDs)
End If
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出")
End 0
核心知识点补充
-
摇杆轴数值范围:
- 物理摇杆轴的原始值范围为
-32768 ~ 32767(对应宏SDL_JOYSTICK_AXIS_MIN/MAX); - 通常需归一化为
-1.0 ~ 1.0便于游戏逻辑处理; - 注意轴的死区处理(过滤微小的硬件漂移值)。
- 物理摇杆轴的原始值范围为
-
摇杆事件类型:
SDL_EVENT_JOYSTICK_AXIS_MOTION:轴移动事件;SDL_EVENT_JOYSTICK_BUTTON_DOWN/UP:按键按下/释放事件;SDL_EVENT_JOYSTICK_HAT_MOTION:方向键状态变化事件;SDL_EVENT_JOYSTICK_ADDED/REMOVED:设备插拔事件。
-
虚拟摇杆使用场景:
- 适配非标准输入设备(如 VR 手柄、自定义控制器);
- 输入录制/回放(录制真实摇杆输入,回放时通过虚拟摇杆模拟);
- 自动化测试(模拟摇杆操作验证游戏逻辑)。
总结
- 适用场景:
- 底层硬件控制、非标准摇杆适配、虚拟摇杆创建 → 使用本模块;
- 标准化游戏手柄交互(如 Xbox/PS 手柄)→ 优先使用 Gamepad API;
- 核心流程:
- 初始化(SDL_INIT_JOYSTICK)→ 枚举设备 → 打开摇杆 → 处理事件/查询状态 → 关闭摇杆;
- 关键注意事项:
- 多线程访问需调用
SDL_LockJoysticks/SDL_UnlockJoysticks保证线程安全; - 虚拟摇杆需手动模拟输入数据,物理摇杆依赖硬件事件;
- 摇杆震动、LED 等功能仅部分设备支持,需做兼容性检查。
- 多线程访问需调用
评论一下?