鼠标子系统(CategoryMouse)
所有图形界面(GUI)应用都需要处理鼠标交互,SDL 提供了一套完整的函数集,用于管理鼠标输入、控制光标显示形态等核心功能。
核心交互方式
- 事件驱动:鼠标操作主要通过事件子系统传递(如移动触发
SDL_EVENT_MOUSE_MOTION、按键按下触发SDL_EVENT_MOUSE_BUTTON_DOWN); - 状态查询:也可通过
SDL_GetMouseState()随时查询鼠标当前状态(位置、按键按下情况)。
特殊场景适配
- 相对鼠标模式:FPS 等游戏需解除光标与窗口边界的绑定(避免鼠标移到窗口边缘停止响应),可调用
SDL_SetWindowRelativeMouseMode()——隐藏光标、独占窗口输入、无边界读取鼠标移动; - 自定义光标:
- 隐藏系统光标,自行绘制:
SDL_HideCursor()/SDL_ShowCursor(); - 更高效的方式:通过
SDL_CreateColorCursor()创建自定义图像光标,或SDL_CreateSystemCursor()使用系统预设光标,再通过SDL_SetCursor()设置;
- 隐藏系统光标,自行绘制:
- 多鼠标设备:多数平台支持识别多个连接的鼠标,可通过
SDL_GetMice()枚举设备,监听SDL_EVENT_MOUSE_ADDED/SDL_EVENT_MOUSE_REMOVED事件感知设备插拔; - 虚拟鼠标:SDL 为触摸/手写笔输入提供虚拟鼠标设备,桌面应用无需修改代码即可在触屏手机上运行;若需区分原生鼠标和触摸/手写笔,可过滤
which字段为SDL_TOUCH_MOUSEID/SDL_PEN_MOUSEID的事件。
函数
- SDL_CaptureMouse:捕获/释放全局鼠标输入(让指定窗口接收所有鼠标事件,即使鼠标在窗口外)
- SDL_CreateAnimatedCursor:创建动画光标(支持多帧切换)
- SDL_CreateColorCursor:创建自定义颜色光标(从 SDL_Surface 生成光标图像)
- SDL_CreateCursor:创建自定义光标(从像素数据生成,兼容旧版接口)
- SDL_CreateSystemCursor:创建系统预设光标(如箭头、手型、等待光标等)
- SDL_CursorVisible:检查光标当前是否可见(返回 1 可见,0 隐藏)
- SDL_DestroyCursor:销毁自定义光标(释放光标资源)
- SDL_GetCursor:获取当前设置的光标(返回 SDL_Cursor 指针)
- SDL_GetDefaultCursor:获取系统默认光标
- SDL_GetGlobalMouseState:获取鼠标的全局屏幕坐标及按键状态(不受窗口约束)
- SDL_GetMice:枚举系统中所有已连接的鼠标设备(返回设备 ID 列表及数量)
- SDL_GetMouseFocus:获取当前拥有鼠标焦点的 SDL 窗口
- SDL_GetMouseNameForID:根据鼠标设备 ID 获取设备名称(如 "USB Optical Mouse")
- SDL_GetMouseState:获取鼠标在指定窗口内的坐标及按键状态
- SDL_GetRelativeMouseState:获取鼠标的相对移动量及按键状态(适用于相对鼠标模式)
- SDL_GetWindowMouseGrab:检查窗口是否启用鼠标抓取(鼠标是否被限制在窗口内)
- SDL_GetWindowMouseRect:获取窗口的鼠标活动区域(鼠标仅在该区域内响应)
- SDL_GetWindowRelativeMouseMode:检查窗口是否启用相对鼠标模式
- SDL_HasMouse:检查系统是否连接了至少一个鼠标设备(返回布尔值)
- SDL_HideCursor:隐藏鼠标光标(全局生效)
- SDL_SetCursor:设置当前使用的光标(切换自定义/系统光标)
- SDL_SetRelativeMouseTransform:设置相对鼠标移动的变换回调(自定义鼠标移动的坐标转换规则)
- SDL_SetWindowMouseGrab:启用/禁用窗口的鼠标抓取(限制鼠标在窗口内移动)
- SDL_SetWindowMouseRect:设置窗口的鼠标活动区域(仅该区域内响应鼠标事件)
- SDL_SetWindowRelativeMouseMode:启用/禁用窗口的相对鼠标模式(FPS 游戏核心接口)
- SDL_ShowCursor:显示鼠标光标(全局生效)
- SDL_WarpMouseGlobal:将鼠标移动到全局屏幕的指定坐标
- SDL_WarpMouseInWindow:将鼠标移动到指定窗口内的指定坐标
数据类型
- SDL_Cursor:光标句柄类型(标识自定义/系统光标)
- SDL_MouseButtonFlags:鼠标按键标志类型(位掩码,标识哪些按键被按下,如 SDL_BUTTON_LMASK 左键、SDL_BUTTON_RMASK 右键)
- SDL_MouseID:鼠标设备唯一标识类型(区分多个连接的鼠标)
- SDL_MouseMotionTransformCallback:鼠标移动变换回调类型(自定义相对鼠标的坐标转换逻辑)
结构体
- SDL_CursorFrameInfo:动画光标帧信息结构体(包含单帧图像、延迟时间等)
枚举
- SDL_MouseWheelDirection:鼠标滚轮方向枚举(SDL_MOUSEWHEEL_NORMAL 正常方向、SDL_MOUSEWHEEL_FLIPPED 反转方向)
- SDL_SystemCursor:系统预设光标类型枚举(如 SDL_SYSTEM_CURSOR_ARROW 箭头、SDL_SYSTEM_CURSOR_HAND 手型、SDL_SYSTEM_CURSOR_WAIT 等待)
宏
- SDL_TOUCH_MOUSEID:触摸输入对应的虚拟鼠标设备 ID(用于区分原生鼠标和触摸模拟鼠标)
FreeBASIC 示例代码
' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"
' 辅助函数:打印鼠标状态信息
Sub PrintMouseState(ByVal window As SDL_Window Ptr)
Dim As Integer x, y
Dim As Uint32 buttonState = SDL_GetMouseState(window, @x, @y)
Dim As String btnStr = ""
If (buttonState And SDL_BUTTON_LMASK) Then btnStr &= "左键 "
If (buttonState And SDL_BUTTON_RMASK) Then btnStr &= "右键 "
If (buttonState And SDL_BUTTON_MMASK) Then btnStr &= "中键 "
If (btnStr = "") Then btnStr = "无按键"
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标位置:(%d, %d) | 按键状态:%s", x, y, btnStr)
End Sub
' 主程序
Dim As SDL_Window Ptr window = NULL
Dim As SDL_Renderer Ptr renderer = NULL
Dim As SDL_Cursor Ptr customCursor = NULL, systemCursor = NULL
Dim As SDL_Event evt
Dim As SDL_MouseID Ptr mouseIDs = NULL
Dim As Integer mouseCount = 0
Dim As Boolean quit = False, relativeMode = 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. 检查鼠标设备
If (Not SDL_HasMouse()) Then
SDL_LogError(SDL_LOG_CATEGORY_INPUT, "系统未检测到鼠标设备")
SDL_Quit()
End 1
End If
' 枚举鼠标设备
mouseCount = SDL_GetMice(0, NULL)
If (mouseCount > 0) Then
mouseIDs = Callocate(mouseCount * SizeOf(SDL_MouseID))
mouseCount = SDL_GetMice(mouseCount, mouseIDs)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "检测到 %d 个鼠标设备", mouseCount)
For i As Integer = 0 To mouseCount - 1
Dim As ZString Ptr mouseName = SDL_GetMouseNameForID(mouseIDs[i])
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标 %d:%s", i, mouseName)
SDL_free(mouseName)
Next
End If
' 3. 创建窗口和渲染器
window = SDL_CreateWindow("SDL 鼠标示例", _
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _
800, 600, SDL_WINDOW_SHOWN)
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED)
If (window = NULL Or renderer = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建窗口/渲染器失败:%s", SDL_GetError())
SDL_Quit()
End 1
End If
' 4. 创建系统预设光标(手型)
systemCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND)
If (systemCursor = NULL) Then
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "创建系统光标失败:%s", SDL_GetError())
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL 鼠标操作示例 ===")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "F1:切换相对鼠标模式 | F2:切换系统光标(手型/默认) | F3:显示/隐藏光标 | ESC:退出")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "鼠标移动/点击:打印状态 | 滚轮:打印方向")
' 5. 主循环
While (Not quit)
' 每帧打印鼠标状态
PrintMouseState(window)
' 处理事件队列
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:切换相对鼠标模式(FPS 模式)
Case SDL_SCANCODE_F1
relativeMode = Not relativeMode
SDL_SetWindowRelativeMouseMode(window, relativeMode)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "相对鼠标模式:%s", IIf(relativeMode, "启用", "禁用"))
' F2:切换系统光标(手型/默认)
Case SDL_SCANCODE_F2
Static As Boolean useCustom = False
useCustom = Not useCustom
If (useCustom And systemCursor <> NULL) Then
SDL_SetCursor(systemCursor)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "切换为手型光标")
Else
SDL_SetCursor(SDL_GetDefaultCursor())
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "切换为默认箭头光标")
End If
' F3:显示/隐藏光标
Case SDL_SCANCODE_F3
Static As Boolean cursorVisible = True
cursorVisible = Not cursorVisible
If (cursorVisible) Then
SDL_ShowCursor(SDL_ENABLE)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "显示光标")
Else
SDL_HideCursor()
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "隐藏光标")
End If
End Select
' 鼠标移动事件
Case SDL_EVENT_MOUSE_MOTION
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标移动:相对偏移 (%d, %d)", evt.motion.xrel, evt.motion.yrel)
' 鼠标按键事件
Case SDL_EVENT_MOUSE_BUTTON_DOWN
Select Case evt.button.button
Case SDL_BUTTON_LEFT: SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "左键按下")
Case SDL_BUTTON_RIGHT: SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "右键按下")
Case SDL_BUTTON_MIDDLE: SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "中键按下")
Case Else: SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "额外按键 %d 按下", evt.button.button)
End Select
' 鼠标滚轮事件
Case SDL_EVENT_MOUSE_WHEEL
Dim As String dir = IIf(evt.wheel.y > 0, "上", "下")
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标滚轮:%s | 水平偏移:%d", dir, evt.wheel.x)
' 鼠标设备插拔事件
Case SDL_EVENT_MOUSE_ADDED
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "检测到新鼠标设备(ID:%d)", evt.mouse.device)
Case SDL_EVENT_MOUSE_REMOVED
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标设备断开(ID:%d)", evt.mouse.device)
End Select
Wend
' 清屏(黑色背景)
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255)
SDL_RenderClear(renderer)
SDL_RenderPresent(renderer)
SDL_Delay(50) ' 降低日志输出频率
Wend
' 6. 清理资源
If (systemCursor <> NULL) Then
SDL_DestroyCursor(systemCursor)
End If
If (mouseIDs <> NULL) Then
Deallocate(mouseIDs)
End If
SDL_DestroyRenderer(renderer)
SDL_DestroyWindow(window)
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出")
End 0
核心知识点补充
-
鼠标按键标识:
SDL_BUTTON_LEFT:左键(1)SDL_BUTTON_RIGHT:右键(3)SDL_BUTTON_MIDDLE:中键/滚轮键(2)SDL_BUTTON_X1/SDL_BUTTON_X2:额外侧键(4/5)
-
相对鼠标模式核心优势:
- 光标隐藏,不会被窗口边界限制;
- 鼠标移动以「相对偏移量」返回,而非绝对坐标;
- 适用于 FPS 游戏视角控制、3D 场景旋转等场景。
-
自定义光标注意事项:
SDL_CreateColorCursor需传入 32 位 RGBA 格式的 SDL_Surface;- 光标热点(hotspot)决定光标图像的「点击点」(如箭头光标的尖端);
- 使用完自定义光标后必须调用
SDL_DestroyCursor释放资源。
总结
- 核心交互模式:
- 基础交互:通过
SDL_GetMouseState查询状态,或监听SDL_EVENT_MOUSE_*事件; - 游戏场景:启用相对鼠标模式(
SDL_SetWindowRelativeMouseMode)实现无边界鼠标控制; - 界面定制:通过
SDL_CreateSystemCursor/SDL_CreateColorCursor自定义光标样式;
- 基础交互:通过
- 关键区分:
SDL_GetMouseState:窗口内绝对坐标;SDL_GetRelativeMouseState:相对移动量(相对模式专用);SDL_GetGlobalMouseState:全局屏幕坐标;
- 多设备支持:通过
SDL_GetMice枚举鼠标设备,监听插拔事件实现多鼠标差异化处理。
评论一下?