论坛的首页
勇芳的软件
教程和帮助
VisualFreeBasic编程文档
勇芳系列软件帮助说明教程
留言或交流
登录
搜索
登录
搜索
勇芳
累计撰写
330
篇文章
累计收到
0
条评论
首页
栏目
论坛的首页
勇芳的软件
教程和帮助
VisualFreeBasic编程文档
勇芳系列软件帮助说明教程
留言或交流
登录
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_音频(CategoryAudio)
音频子系统(CategoryAudio) SDL 库的音频功能模块,SDL3 中所有音频相关操作均围绕 SDL_AudioStream(音频流)展开。无论是播放/录制音频、格式转换、流式传输、数据缓冲还是混音,都需要通过音频流来处理。 音频流核心特性 音频流具备高度灵活性: 可随时接收任意数量的音频数据,支持所有SDL兼容格式; 可按需输出任意目标格式的音频数据,即使在处理过程中输入/输出格式发生变化; 应用程序可打开音频设备并绑定多个音频流,按需向流中填充数据; 当设备需要音频数据时,会从所有绑定的流中拉取数据并混音后播放; 支持通过应用提供的回调函数按需获取数据,与SDL2的音频模型高度兼容。 SDL 还提供了简易的 WAV 文件加载接口:SDL_LoadWAV(从文件加载)和 SDL_LoadWAV_IO(从自定义IO加载),作为程序加载音频数据的基础方式。 逻辑音频设备 在SDL3中,打开物理音频设备(如声卡)会得到一个逻辑设备ID,可绑定音频流使用。核心特性: 隔离性:每次打开设备都会生成新的逻辑设备,程序不同模块(如语音库、混音器)的音频操作互不干扰; 自动混音:SDL会在底层将所有逻辑设备的音频混合为单个缓冲区,输出到物理设备; 资源优化:SDL仅在系统层面打开物理设备一次,内部管理所有逻辑设备; 动态迁移:使用默认设备时,SDL可自动将逻辑设备迁移到新的物理硬件(如插入耳机、系统默认设备变更),无需程序干预。 简化音频模型 针对仅需单音频源的场景,SDL提供 SDL_OpenAudioDeviceStream 简化接口: 单个函数完成:打开音频设备 → 创建音频流 → 绑定流到设备 → (可选)设置数据回调; 核心操作接口为 SDL_AudioStream,设备句柄被隐藏; 销毁该函数创建的流会自动关闭设备,且无法修改流绑定关系; 设备默认处于暂停状态,需显式恢复播放(非简化模型中,设备默认未暂停,绑定流后即播放)。 声道布局 SDL处理的音频数据为非压缩的交错式PCM数据(可通过第三方解码器处理MP3等格式,SDL不内置解码)。各声道数据在内存中有固定排列顺序: 缩写说明 FRONT = 单声道扬声器 FL = 前置左声道 FR = 前置右声道 FC = 前置中声道 BL = 后置左声道 BR = 后置右声道 SR = 环绕右声道 SL = 环绕左声道 BC = 后置中声道 LFE = 低频声道(重低音) 标准声道布局(内存排列顺序) 1声道(单声道):FRONT 2声道(立体声):FL, FR 3声道(2.1):FL, FR, LFE 4声道(四声道):FL, FR, BL, BR 5声道(4.1):FL, FR, LFE, BL, BR 6声道(5.1):FL, FR, FC, LFE, BL, BR(后两个也可为SL, SR) 7声道(6.1):FL, FR, FC, LFE, BC, SL, SR 8声道(7.1):FL, FR, FC, LFE, BL, BR, SL, SR 该顺序与DirectSound一致,SDL会自动适配不同平台的声道顺序要求。SDL_AudioStream 也支持自定义声道映射,满足特殊音频处理需求。 函数 SDL_AudioDevicePaused:检查音频设备是否处于暂停状态(返回布尔值) SDL_AudioStreamDevicePaused:检查音频流绑定的设备是否暂停 SDL_BindAudioStream:将单个音频流绑定到指定音频设备(混音播放) SDL_BindAudioStreams:批量绑定多个音频流到指定音频设备 SDL_ClearAudioStream:清空音频流中的所有缓冲数据 SDL_CloseAudioDevice:关闭音频设备(释放设备资源,停止播放) SDL_ConvertAudioSamples:直接转换音频样本格式(单次转换,无需创建流) SDL_CreateAudioStream:创建音频流(指定输入/输出格式、采样率、声道数) SDL_DestroyAudioStream:销毁音频流(释放内存,若为简化模型创建则关闭设备) SDL_FlushAudioStream:刷新音频流(强制输出所有缓冲数据) SDL_GetAudioDeviceChannelMap:获取音频设备的声道映射表 SDL_GetAudioDeviceFormat:获取音频设备的当前格式(采样率、声道数、格式) SDL_GetAudioDeviceGain:获取音频设备的增益(音量,0~1.0) SDL_GetAudioDeviceName:通过设备ID获取音频设备名称 SDL_GetAudioDriver:获取指定索引的音频驱动名称 SDL_GetAudioFormatName:获取音频格式的可读名称(如 "SDL_AUDIO_F32") SDL_GetAudioPlaybackDevices:枚举系统中所有音频播放设备(返回设备ID列表) SDL_GetAudioRecordingDevices:枚举系统中所有音频录制设备(返回设备ID列表) SDL_GetAudioStreamAvailable:获取音频流中可读取的输出数据字节数 SDL_GetAudioStreamData:从音频流读取转换后的音频数据 SDL_GetAudioStreamDevice:获取音频流绑定的音频设备ID SDL_GetAudioStreamFormat:获取音频流的输入/输出格式信息 SDL_GetAudioStreamFrequencyRatio:获取音频流的采样率转换比率 SDL_GetAudioStreamGain:获取音频流的增益(独立于设备增益) SDL_GetAudioStreamInputChannelMap:获取音频流的输入声道映射 SDL_GetAudioStreamOutputChannelMap:获取音频流的输出声道映射 SDL_GetAudioStreamProperties:获取音频流的属性集合(延迟、缓冲等) SDL_GetAudioStreamQueued:获取音频流中待处理的输入数据字节数 SDL_GetCurrentAudioDriver:获取当前使用的音频驱动名称 SDL_GetNumAudioDrivers:获取系统中可用的音频驱动数量 SDL_GetSilenceValueForFormat:获取指定音频格式的静音值(用于填充静音数据) SDL_IsAudioDevicePhysical:检查音频设备是否为物理设备(非逻辑设备) SDL_IsAudioDevicePlayback:检查音频设备是否为播放设备(非录制设备) SDL_LoadWAV:从文件加载WAV音频数据(返回音频格式和数据缓冲区) SDL_LoadWAV_IO:从自定义IO流加载WAV音频数据 SDL_LockAudioStream:锁定音频流(线程安全操作) SDL_MixAudio:混音多个音频缓冲区(按指定格式混合) SDL_OpenAudioDevice:打开音频设备(返回逻辑设备ID) SDL_OpenAudioDeviceStream:简化接口,一键创建设备+流并绑定 SDL_PauseAudioDevice:暂停音频设备(停止播放,保留缓冲数据) SDL_PauseAudioStreamDevice:暂停音频流绑定的设备 SDL_PutAudioStreamData:向音频流写入输入数据(进行格式转换) SDL_PutAudioStreamDataNoCopy:写入数据但不拷贝(直接使用原缓冲区) SDL_PutAudioStreamPlanarData:向音频流写入平面格式音频数据(非交错) SDL_ResumeAudioDevice:恢复暂停的音频设备(继续播放) SDL_ResumeAudioStreamDevice:恢复音频流绑定设备的播放 SDL_SetAudioDeviceGain:设置音频设备的增益(调整整体音量) SDL_SetAudioPostmixCallback:设置音频后混音回调(最终输出前处理) SDL_SetAudioStreamFormat:动态修改音频流的输入/输出格式 SDL_SetAudioStreamFrequencyRatio:设置音频流的采样率转换比率 SDL_SetAudioStreamGain:设置音频流的增益(仅影响该流的音量) SDL_SetAudioStreamGetCallback:设置音频流的数据源回调(按需获取数据) SDL_SetAudioStreamInputChannelMap:设置音频流的输入声道映射 SDL_SetAudioStreamOutputChannelMap:设置音频流的输出声道映射 SDL_SetAudioStreamPutCallback:设置音频流的数据输出回调(数据就绪时触发) SDL_UnbindAudioStream:解除音频流与设备的绑定 SDL_UnbindAudioStreams:批量解除多个音频流的绑定 SDL_UnlockAudioStream:解锁音频流(与SDL_LockAudioStream配对使用) 数据类型 SDL_AudioDeviceID:音频设备标识类型(区分不同的逻辑/物理设备) SDL_AudioPostmixCallback:音频后混音回调函数类型(处理最终输出数据) SDL_AudioStream:音频流句柄类型(标识已创建的音频流) SDL_AudioStreamCallback:音频流数据回调函数类型(按需提供/处理数据) SDL_AudioStreamDataCompleteCallback:音频流数据完成回调类型(数据处理完毕触发) 结构体 SDL_AudioSpec:音频格式描述结构体(包含采样率、声道数、格式、样本数等) 枚举 SDL_AudioFormat:音频格式枚举(如SDL_AUDIO_U8、SDL_AUDIO_S16、SDL_AUDIO_F32等) 宏 SDL_AUDIO_BITSIZE:提取音频格式的位深度(如16位、32位) SDL_AUDIO_BYTESIZE:计算音频格式的字节大小(位深度/8) SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK:默认音频播放设备标识 SDL_AUDIO_DEVICE_DEFAULT_RECORDING:默认音频录制设备标识 SDL_AUDIO_FRAMESIZE:计算单帧音频数据的字节大小(声道数×字节大小) SDL_AUDIO_ISBIGENDIAN:检查音频格式是否为大端序 SDL_AUDIO_ISFLOAT:检查音频格式是否为浮点型 SDL_AUDIO_ISINT:检查音频格式是否为整型 SDL_AUDIO_ISLITTLEENDIAN:检查音频格式是否为小端序 SDL_AUDIO_ISSIGNED:检查音频格式是否为有符号类型 SDL_AUDIO_ISUNSIGNED:检查音频格式是否为无符号类型 SDL_AUDIO_MASK_BIG_ENDIAN:大端序格式掩码 SDL_AUDIO_MASK_BITSIZE:位深度格式掩码 SDL_AUDIO_MASK_FLOAT:浮点型格式掩码 SDL_AUDIO_MASK_SIGNED:有符号类型格式掩码 SDL_DEFINE_AUDIO_FORMAT:自定义音频格式宏(组合位深度、字节序、类型等) FreeBASIC 示例代码 ' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库) #Include "SDL.bi" ' 播放WAV文件示例 Sub PlayWAVFile(ByVal filename As String) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "尝试播放WAV文件:%s", filename) ' 1. 加载WAV文件 Dim As SDL_AudioSpec wavSpec Dim As UByte Ptr wavBuffer = NULL Dim As Uint64 wavLength = 0 If (SDL_LoadWAV(filename, @wavSpec, @wavBuffer, @wavLength) = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "加载WAV文件失败:%s", SDL_GetError()) Return End If SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "WAV文件加载成功:") SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " 采样率:%d Hz", wavSpec.freq) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " 声道数:%d", wavSpec.channels) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " 格式:%s", SDL_GetAudioFormatName(wavSpec.format)) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " 数据长度:%llu 字节", wavLength) ' 2. 获取默认播放设备 Dim As SDL_AudioDeviceID defaultDevice = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK ' 3. 打开音频设备(使用WAV文件的格式) Dim As SDL_AudioDeviceID devID = SDL_OpenAudioDevice(defaultDevice, @wavSpec, 0) If (devID = 0) Then SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "打开音频设备失败:%s", SDL_GetError()) SDL_FreeWAV(wavBuffer) Return End If ' 4. 创建音频流(转换为设备兼容格式) Dim As SDL_AudioStream Ptr stream = SDL_CreateAudioStream( _ wavSpec.format, wavSpec.channels, wavSpec.freq, _ wavSpec.format, wavSpec.channels, wavSpec.freq _ ) If (stream = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "创建音频流失败:%s", SDL_GetError()) SDL_CloseAudioDevice(devID) SDL_FreeWAV(wavBuffer) Return End If ' 5. 向音频流写入WAV数据 SDL_PutAudioStreamData(stream, wavBuffer, wavLength) SDL_FlushAudioStream(stream) ' 刷新流,确保所有数据可输出 ' 6. 绑定音频流到设备 If (SDL_BindAudioStream(devID, stream) < 0) Then SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "绑定音频流失败:%s", SDL_GetError()) SDL_DestroyAudioStream(stream) SDL_CloseAudioDevice(devID) SDL_FreeWAV(wavBuffer) Return End If ' 7. 恢复设备播放(默认暂停) SDL_ResumeAudioDevice(devID) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "开始播放音频...") ' 8. 等待播放完成(检查流中是否还有数据) While (SDL_GetAudioStreamAvailable(stream) > 0) SDL_Delay(100) Wend SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "音频播放完成") ' 9. 清理资源 SDL_UnbindAudioStream(devID, stream) SDL_DestroyAudioStream(stream) SDL_CloseAudioDevice(devID) SDL_FreeWAV(wavBuffer) End Sub ' 简化音频播放示例(使用SDL_OpenAudioDeviceStream) Sub PlayAudioSimplified() SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, vbCrLf & "==== 简化音频播放示例 ====") ' 定义音频格式(44.1kHz,立体声,32位浮点) Dim As SDL_AudioSpec spec spec.freq = 44100 spec.format = SDL_AUDIO_F32 spec.channels = 2 spec.samples = 4096 ' 缓冲区大小 ' 1. 打开简化音频流 Dim As SDL_AudioStream Ptr stream = SDL_OpenAudioDeviceStream( _ SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, _ @spec, _ NULL, NULL _ ) If (stream = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "打开简化音频流失败:%s", SDL_GetError()) Return End If ' 2. 生成测试音频(正弦波,440Hz A调) Const sampleCount As Integer = 44100 * 2 ' 2秒音频 Dim As Single Ptr audioData = Allocate(sampleCount * 2 * SizeOf(Single)) If (audioData = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "分配音频数据内存失败") SDL_DestroyAudioStream(stream) Return End If ' 生成立体声正弦波 For i As Integer = 0 To sampleCount - 1 Dim As Single t = i / 44100.0 Dim As Single val = Sin(2 * 3.14159265 * 440 * t) * 0.5 ' 0.5音量 ' 左右声道相同 audioData[i * 2] = val audioData[i * 2 + 1] = val Next ' 3. 写入音频数据到流 SDL_PutAudioStreamData(stream, audioData, sampleCount * 2 * SizeOf(Single)) SDL_FlushAudioStream(stream) ' 4. 恢复播放 SDL_ResumeAudioStreamDevice(stream) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "播放2秒440Hz正弦波...") ' 5. 等待播放完成 While (SDL_GetAudioStreamAvailable(stream) > 0) SDL_Delay(100) Wend ' 6. 清理资源 Deallocate(audioData) SDL_DestroyAudioStream(stream) ' 销毁流自动关闭设备 SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "简化音频播放完成") End Sub ' 枚举音频设备示例 Sub EnumerateAudioDevices() SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, vbCrLf & "==== 枚举音频设备 ====") ' 枚举播放设备 Dim As Integer playbackCount = SDL_GetAudioPlaybackDevices(NULL, 0) If (playbackCount > 0) Then Dim As SDL_AudioDeviceID Ptr playbackDevs = Callocate(playbackCount * SizeOf(SDL_AudioDeviceID)) playbackCount = SDL_GetAudioPlaybackDevices(playbackDevs, playbackCount) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "播放设备(%d个):", playbackCount) For i As Integer = 0 To playbackCount - 1 Dim As ZString Ptr devName = SDL_GetAudioDeviceName(playbackDevs[i]) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " %d: %s (物理设备:%s)", _ i, devName, IIf(SDL_IsAudioDevicePhysical(playbackDevs[i]), "是", "否")) If (devName <> NULL) Then SDL_free(devName) Next Deallocate(playbackDevs) Else SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "未检测到音频播放设备") End If ' 枚举录制设备 Dim As Integer recordCount = SDL_GetAudioRecordingDevices(NULL, 0) If (recordCount > 0) Then Dim As SDL_AudioDeviceID Ptr recordDevs = Callocate(recordCount * SizeOf(SDL_AudioDeviceID)) recordCount = SDL_GetAudioRecordingDevices(recordDevs, recordCount) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "录制设备(%d个):", recordCount) For i As Integer = 0 To recordCount - 1 Dim As ZString Ptr devName = SDL_GetAudioDeviceName(recordDevs[i]) SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, " %d: %s", i, devName) If (devName <> NULL) Then SDL_free(devName) Next Deallocate(recordDevs) Else SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "未检测到音频录制设备") End If End Sub ' 主程序 Sub Main() ' 初始化SDL音频子系统 If (SDL_Init(SDL_INIT_AUDIO) < 0) Then SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL初始化失败:%s", SDL_GetError()) Exit Sub End If SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL3 音频示例程序 ===") ' 1. 枚举音频设备 EnumerateAudioDevices() ' 2. 播放WAV文件(替换为实际的WAV文件路径) Dim As String wavFile = "test.wav" If (SDL_FileExists(wavFile)) Then PlayWAVFile(wavFile) Else SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO, "WAV文件不存在:%s,跳过播放", wavFile) End If ' 3. 简化音频播放(生成正弦波) PlayAudioSimplified() ' 退出SDL SDL_Quit() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序结束") End Sub ' 辅助函数:检查文件是否存在 Function SDL_FileExists(ByVal filename As String) As Boolean Dim As FILE Ptr f = fopen(filename, "rb") If (f <> NULL) Then fclose(f) Return True End If Return False End Function ' 运行主程序 Main() 核心知识点补充 音频格式说明: 格式常量 描述 位深度 类型 字节序 SDL_AUDIO_U8 无符号8位整型 8 整型 - SDL_AUDIO_S16 有符号16位整型 16 整型 小端序 SDL_AUDIO_S32 有符号32位整型 32 整型 小端序 SDL_AUDIO_F32 32位浮点型 32 浮点型 小端序 SDL_AUDIO_S16MSB 有符号16位整型 16 整型 大端序 开发注意事项: 数据对齐:交错式PCM数据中,每个采样点的各声道数据连续排列(如立体声:左、右、左、右...); 缓冲区大小:spec.samples 建议设置为2的幂(如1024、2048、4096),避免音频卡顿; 线程安全:音频流操作需加锁(SDL_LockAudioStream/SDL_UnlockAudioStream); 资源释放:加载WAV后必须调用 SDL_FreeWAV,避免内存泄漏; 跨平台兼容:不同平台的默认音频格式可能不同,建议使用浮点格式(SDL_AUDIO_F32)提高兼容性。 性能优化技巧: 批量写入/读取音频数据,减少频繁的 SDL_PutAudioStreamData/SDL_GetAudioStreamData 调用; 使用 SDL_PutAudioStreamDataNoCopy 避免数据拷贝,提升性能; 合理设置缓冲区大小,平衡延迟和卡顿风险; 多个音频源使用不同的音频流,通过SDL自动混音,避免手动混音的性能损耗。 总结 核心优势: SDL3 以 SDL_AudioStream 为核心,统一了音频处理流程,简化格式转换、混音等操作; 逻辑设备机制实现了音频隔离和自动混音,简化多音频源管理; 提供简化接口和完整接口两套API,兼顾易用性和灵活性; 跨平台统一的声道布局,自动适配不同系统的音频硬件。 使用建议: 简单音频播放优先使用 SDL_OpenAudioDeviceStream 简化接口; 多音频源场景使用逻辑设备+音频流绑定,利用SDL自动混音; 音频格式转换必须通过音频流,避免直接操作原始数据; 播放WAV文件后务必释放缓冲区,设备使用完毕及时关闭。 关键接口: 设备管理:SDL_OpenAudioDevice/SDL_CloseAudioDevice/SDL_GetAudioPlaybackDevices; 音频流:SDL_CreateAudioStream/SDL_PutAudioStreamData/SDL_GetAudioStreamData; 简化播放:SDL_OpenAudioDeviceStream; WAV加载:SDL_LoadWAV/SDL_FreeWAV。
2026年-3月-6日
2 阅读
0 评论
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_触觉反馈(CategoryHaptic)
触觉反馈子系统(CategoryHaptic) SDL 触觉反馈(Haptic)子系统用于管理力反馈(force feedback)设备,如带震动功能的游戏手柄、方向盘、力反馈鼠标等,可实现震动、力反馈等触觉效果。 基础使用流程 初始化子系统(传入 SDL_INIT_HAPTIC 标志); 打开触觉反馈设备: 通过索引打开:SDL_OpenHaptic(); 从已打开的游戏手柄打开:SDL_OpenHapticFromJoystick(); 创建触觉效果(定义 SDL_HapticEffect 结构体); 上传效果:SDL_CreateHapticEffect(); 运行效果:SDL_RunHapticEffect(); (可选)释放效果:SDL_DestroyHapticEffect(); 关闭设备:SDL_CloseHaptic()。 简单震动示例(C语言) SDL_Haptic *haptic = NULL; // 打开设备 SDL_HapticID *haptics = SDL_GetHaptics(NULL); if (haptics) { haptic = SDL_OpenHaptic(haptics[0]); SDL_free(haptics); } if (haptic == NULL) return; // 初始化简易震动 if (!SDL_InitHapticRumble(haptic)) return; // 以50%强度播放震动效果,持续2秒 if (!SDL_PlayHapticRumble(haptic, 0.5, 2000)) return; SDL_Delay(2000); // 清理资源 SDL_CloseHaptic(haptic); 完整效果示例(C语言) bool test_haptic(SDL_Joystick *joystick) { SDL_Haptic *haptic; SDL_HapticEffect effect; SDL_HapticEffectID effect_id; // 从游戏手柄打开触觉设备 haptic = SDL_OpenHapticFromJoystick(joystick); if (haptic == NULL) return false; // 大概率手柄不支持触觉反馈 // 检查是否支持正弦波效果 if ((SDL_GetHapticFeatures(haptic) & SDL_HAPTIC_SINE)==0) { SDL_CloseHaptic(haptic); // 不支持正弦效果 return false; } // 创建效果 SDL_memset(&effect, 0, sizeof(SDL_HapticEffect)); // 0为安全默认值 effect.type = SDL_HAPTIC_SINE; effect.periodic.direction.type = SDL_HAPTIC_POLAR; // 极坐标 effect.periodic.direction.dir[0] = 18000; // 力来自南方(180度) effect.periodic.period = 1000; // 周期1000毫秒 effect.periodic.magnitude = 20000; // 强度 20000/32767 effect.periodic.length = 5000; // 持续5秒 effect.periodic.attack_length = 1000; // 1秒达到最大强度 effect.periodic.fade_length = 1000; // 1秒渐弱至消失 // 上传效果 effect_id = SDL_CreateHapticEffect(haptic, &effect); // 测试效果 SDL_RunHapticEffect(haptic, effect_id, 1); SDL_Delay(5000); // 等待效果结束 // 销毁效果(关闭设备时也会自动销毁) SDL_DestroyHapticEffect(haptic, effect_id); // 关闭设备 SDL_CloseHaptic(haptic); return true; // 成功 } 注意:SDL 触觉反馈子系统非线程安全,需确保所有操作在同一线程执行。 函数 SDL_CloseHaptic:关闭已打开的触觉反馈设备(释放设备资源,终止力反馈效果) SDL_CreateHapticEffect:上传触觉效果到设备(将定义的效果参数写入硬件,返回效果ID) SDL_DestroyHapticEffect:销毁已上传的触觉效果(释放设备中该效果的资源) SDL_GetHapticEffectStatus:获取触觉效果的运行状态(如是否正在播放、暂停) SDL_GetHapticFeatures:获取触觉设备支持的功能特性(返回位掩码,如是否支持正弦波、震动等) SDL_GetHapticFromID:通过触觉设备ID获取对应的 SDL_Haptic 指针 SDL_GetHapticID:获取已打开触觉设备的唯一标识ID SDL_GetHapticName:获取已打开触觉设备的名称 SDL_GetHapticNameForID:通过触觉设备ID获取设备名称 SDL_GetHaptics:枚举系统中所有触觉设备(返回设备ID列表及数量) SDL_GetMaxHapticEffects:获取设备支持的最大效果数量(硬件可存储的效果总数) SDL_GetMaxHapticEffectsPlaying:获取设备可同时播放的最大效果数量 SDL_GetNumHapticAxes:获取触觉设备的轴数量(如X/Y/Z轴) SDL_HapticEffectSupported:检查设备是否支持指定类型的触觉效果 SDL_HapticRumbleSupported:检查设备是否支持简易震动(Rumble)功能 SDL_InitHapticRumble:初始化设备的简易震动功能(启用基础震动模式) SDL_IsJoystickHaptic:检查指定游戏手柄是否支持触觉反馈 SDL_IsMouseHaptic:检查指定鼠标是否支持触觉反馈 SDL_OpenHaptic:通过设备索引打开触觉反馈设备 SDL_OpenHapticFromJoystick:从已打开的游戏手柄打开触觉反馈设备 SDL_OpenHapticFromMouse:从已打开的鼠标打开触觉反馈设备 SDL_PauseHaptic:暂停设备上所有正在播放的触觉效果 SDL_PlayHapticRumble:播放简易震动效果(指定强度和持续时间) SDL_ResumeHaptic:恢复暂停的触觉效果 SDL_RunHapticEffect:运行指定的触觉效果(可指定循环次数) SDL_SetHapticAutocenter:设置触觉设备的自动居中强度(如方向盘自动回中) SDL_SetHapticGain:设置触觉设备的增益(整体强度缩放,0~100%) SDL_StopHapticEffect:停止指定的触觉效果 SDL_StopHapticEffects:停止设备上所有正在播放的触觉效果 SDL_StopHapticRumble:停止正在播放的简易震动效果 SDL_UpdateHapticEffect:更新已上传的触觉效果参数(修改效果属性) 数据类型 SDL_Haptic:触觉反馈设备句柄类型(标识已打开的触觉设备) SDL_HapticDirectionType:触觉效果方向类型(如笛卡尔坐标、极坐标、球坐标) SDL_HapticEffectID:触觉效果ID类型(标识设备中已上传的具体效果) SDL_HapticEffectType:触觉效果类型(如正弦波、方波、震动、常量力等) SDL_HapticID:触觉设备ID类型(区分系统中的不同触觉设备) 结构体 SDL_HapticCondition:条件触觉效果结构体(如弹簧、阻尼、惯性等效果参数) SDL_HapticConstant:常量力触觉效果结构体(持续恒定的力反馈) SDL_HapticCustom:自定义触觉效果结构体(用户定义的波形数据) SDL_HapticDirection:触觉效果方向结构体(定义力的方向) SDL_HapticEffect:通用触觉效果结构体(包含所有类型效果的参数) SDL_HapticLeftRight:左右声道震动效果结构体(手柄左右电机独立控制) SDL_HapticPeriodic:周期性触觉效果结构体(正弦波、方波、三角波等) SDL_HapticRamp:渐变力触觉效果结构体(力从初始值线性变化到目标值) 枚举 (无) 宏 SDL_HAPTIC_AUTOCENTER:自动居中功能标识(用于 SDL_GetHapticFeatures) SDL_HAPTIC_CARTESIAN:笛卡尔坐标方向类型(X/Y/Z轴) SDL_HAPTIC_CONSTANT:常量力效果类型 SDL_HAPTIC_CUSTOM:自定义效果类型 SDL_HAPTIC_DAMPER:阻尼效果类型(阻力随速度增加) SDL_HAPTIC_FRICTION:摩擦力效果类型(恒定阻力) SDL_HAPTIC_GAIN:增益功能标识 SDL_HAPTIC_INERTIA:惯性效果类型(阻力随加速度增加) SDL_HAPTIC_INFINITY:无限循环标识(效果持续播放直到停止) SDL_HAPTIC_LEFTRIGHT:左右震动效果类型(手柄双电机) SDL_HAPTIC_PAUSE:暂停状态标识 SDL_HAPTIC_POLAR:极坐标方向类型(角度/半径) SDL_HAPTIC_RAMP:渐变力效果类型 SDL_HAPTIC_RESERVED1/2/3:预留标识 SDL_HAPTIC_SAWTOOTHDOWN:锯齿波(下降)效果类型 SDL_HAPTIC_SAWTOOTHUP:锯齿波(上升)效果类型 SDL_HAPTIC_SINE:正弦波效果类型 SDL_HAPTIC_SPHERICAL:球坐标方向类型(方位角/仰角) SDL_HAPTIC_SPRING:弹簧效果类型(力随位移增加) SDL_HAPTIC_SQUARE:方波效果类型 SDL_HAPTIC_STATUS:状态查询标识 SDL_HAPTIC_STEERING_AXIS:方向盘轴标识 SDL_HAPTIC_TRIANGLE:三角波效果类型 FreeBASIC 示例代码 ' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库) #Include "SDL.bi" ' 简易震动效果示例 Sub TestSimpleRumble() SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 测试简易震动效果 ====") ' 1. 枚举触觉设备 Dim As Integer hapticCount = SDL_GetHaptics(NULL, 0) If (hapticCount = 0) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "未检测到触觉反馈设备:%s", SDL_GetError()) Return End If ' 分配内存存储设备ID Dim As SDL_HapticID Ptr hapticIDs = Callocate(hapticCount * SizeOf(SDL_HapticID)) If (hapticIDs = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "分配内存失败") Return End If ' 获取设备ID列表 hapticCount = SDL_GetHaptics(hapticIDs, hapticCount) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "检测到 %d 个触觉设备", hapticCount) ' 2. 打开第一个触觉设备 Dim As SDL_Haptic Ptr haptic = SDL_OpenHaptic(hapticIDs[0]) If (haptic = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "打开触觉设备失败:%s", SDL_GetError()) Deallocate(hapticIDs) Return End If ' 获取设备信息 Dim As ZString Ptr hapticName = SDL_GetHapticName(haptic) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "已打开设备:%s", hapticName) ' 3. 检查是否支持震动 If (Not SDL_HapticRumbleSupported(haptic)) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "该设备不支持简易震动功能") SDL_CloseHaptic(haptic) Deallocate(hapticIDs) If (hapticName <> NULL) Then SDL_free(hapticName) Return End If ' 4. 初始化震动功能 If (Not SDL_InitHapticRumble(haptic)) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "初始化震动功能失败:%s", SDL_GetError()) SDL_CloseHaptic(haptic) Deallocate(hapticIDs) If (hapticName <> NULL) Then SDL_free(hapticName) Return End If ' 5. 播放不同强度的震动 SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "播放 25% 强度震动(1秒)") SDL_PlayHapticRumble(haptic, 0.25, 1000) SDL_Delay(1000) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "播放 75% 强度震动(2秒)") SDL_PlayHapticRumble(haptic, 0.75, 2000) SDL_Delay(2000) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "播放 100% 强度震动(0.5秒)") SDL_PlayHapticRumble(haptic, 1.0, 500) SDL_Delay(500) ' 6. 停止震动 SDL_StopHapticRumble(haptic) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "已停止震动") ' 7. 清理资源 SDL_CloseHaptic(haptic) Deallocate(hapticIDs) If (hapticName <> NULL) Then SDL_free(hapticName) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "简易震动测试完成") End Sub ' 复杂正弦波效果示例 Sub TestSineEffect() SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, vbCrLf & "==== 测试正弦波触觉效果 ====") ' 1. 枚举并打开第一个触觉设备 Dim As SDL_HapticID Ptr hapticIDs = SDL_GetHaptics(NULL) If (hapticIDs = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "未检测到触觉设备:%s", SDL_GetError()) Return End If Dim As SDL_Haptic Ptr haptic = SDL_OpenHaptic(hapticIDs[0]) SDL_free(hapticIDs) If (haptic = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "打开触觉设备失败:%s", SDL_GetError()) Return End If ' 2. 检查是否支持正弦波效果 Dim As Uint32 features = SDL_GetHapticFeatures(haptic) If ((features And SDL_HAPTIC_SINE) = 0) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "该设备不支持正弦波触觉效果") SDL_CloseHaptic(haptic) Return End If SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "设备支持正弦波效果") ' 3. 定义正弦波效果 Dim As SDL_HapticEffect effect SDL_memset(@effect, 0, SizeOf(SDL_HapticEffect)) ' 设置效果类型为正弦波 effect.type = SDL_HAPTIC_SINE ' 设置方向(极坐标,180度=南方) effect.periodic.direction.type = SDL_HAPTIC_POLAR effect.periodic.direction.dir[0] = 18000 ' 角度(0~35999) effect.periodic.direction.dir[1] = 0 ' 半径(仅球坐标使用) ' 正弦波参数 effect.periodic.period = 500 ' 周期(毫秒) effect.periodic.magnitude = 16383 ' 幅度(0~32767,50%强度) effect.periodic.offset = 0 ' 偏移量 effect.periodic.phase = 0 ' 相位(0~35999) ' 效果时长 effect.periodic.length = 3000 ' 总时长(毫秒) effect.periodic.attack_length = 500 ' 攻击时长(渐强,毫秒) effect.periodic.fade_length = 500 ' 衰减时长(渐弱,毫秒) effect.periodic.attack_level = 0 ' 攻击起始强度 effect.periodic.fade_level = 0 ' 衰减结束强度 ' 4. 上传效果 Dim As SDL_HapticEffectID effectID = SDL_CreateHapticEffect(haptic, @effect) If (effectID < 0) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "上传效果失败:%s", SDL_GetError()) SDL_CloseHaptic(haptic) Return End If SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "成功上传正弦波效果(ID:%d)", effectID) ' 5. 运行效果(播放1次) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "播放正弦波效果(3秒)") If (SDL_RunHapticEffect(haptic, effectID, 1) < 0) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "运行效果失败:%s", SDL_GetError()) Else SDL_Delay(3000) ' 等待效果结束 End If ' 6. 销毁效果 SDL_DestroyHapticEffect(haptic, effectID) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "已销毁正弦波效果") ' 7. 清理资源 SDL_CloseHaptic(haptic) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "复杂效果测试完成") End Sub ' 从游戏手柄打开触觉设备示例 Sub TestJoystickHaptic() SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, vbCrLf & "==== 测试游戏手柄触觉反馈 ====") ' 1. 枚举游戏手柄 Dim As Integer joystickCount = SDL_GetJoysticks(NULL, 0) If (joystickCount = 0) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "未检测到游戏手柄:%s", SDL_GetError()) Return End If ' 2. 打开第一个游戏手柄 Dim As SDL_JoystickID Ptr joystickIDs = SDL_GetJoysticks(NULL) Dim As SDL_Joystick Ptr joystick = SDL_OpenJoystick(joystickIDs[0]) SDL_free(joystickIDs) If (joystick = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "打开游戏手柄失败:%s", SDL_GetError()) Return End If ' 3. 检查手柄是否支持触觉反馈 If (Not SDL_IsJoystickHaptic(joystick)) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "该游戏手柄不支持触觉反馈") SDL_CloseJoystick(joystick) Return End If ' 4. 从手柄打开触觉设备 Dim As SDL_Haptic Ptr haptic = SDL_OpenHapticFromJoystick(joystick) If (haptic = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "从手柄打开触觉设备失败:%s", SDL_GetError()) SDL_CloseJoystick(joystick) Return End If SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "成功从游戏手柄打开触觉设备") ' 5. 测试简易震动 If (SDL_HapticRumbleSupported(haptic)) Then SDL_InitHapticRumble(haptic) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "手柄震动测试(1.5秒)") SDL_PlayHapticRumble(haptic, 0.6, 1500) SDL_Delay(1500) SDL_StopHapticRumble(haptic) End If ' 6. 清理资源 SDL_CloseHaptic(haptic) SDL_CloseJoystick(joystick) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "游戏手柄触觉测试完成") End Sub ' 主程序 Sub Main() ' 初始化 SDL(包含触觉和游戏手柄子系统) If (SDL_Init(SDL_INIT_HAPTIC Or SDL_INIT_JOYSTICK) < 0) Then SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError()) Exit Sub End If SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL 触觉反馈示例程序 ===") ' 测试简易震动 TestSimpleRumble() ' 测试复杂正弦波效果 TestSineEffect() ' 测试游戏手柄触觉反馈 TestJoystickHaptic() ' 退出 SDL SDL_Quit() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序结束") End Sub ' 运行主程序 Main() 核心知识点补充 触觉效果类型说明: 效果类型 特点 适用场景 简易震动(Rumble) 双电机独立控制强度 游戏中的碰撞、爆炸 正弦波(Sine) 平滑的周期性震动 发动机震动、心跳 方波(Square) 强烈的脉冲式震动 枪声、撞击 常量力(Constant) 持续的恒定力 方向盘力反馈、阻力 渐变力(Ramp) 线性变化的力 加速/减速感 弹簧/阻尼 物理模拟的力反馈 赛车游戏方向盘 开发注意事项: 强度范围:触觉效果的强度值范围为 0~32767(16位有符号整数),0 为无效果,32767 为最大强度; 方向设置:极坐标的角度范围为 0~35999(对应 0~360 度),18000 对应 180 度(南方); 跨平台兼容:Windows 平台对触觉效果支持最好,Linux/macOS 次之,移动平台仅部分手柄支持; 线程安全:所有触觉操作必须在同一线程执行,避免多线程同时访问 SDL_Haptic 句柄。 调试技巧: 使用 SDL_GetHapticFeatures 检查设备支持的效果类型,避免创建不支持的效果; 通过 SDL_SetHapticGain 调整整体强度,适配不同设备的震动灵敏度; 复杂效果建议先测试简易震动功能,确认设备可正常工作后再开发。 总结 核心优势: SDL 统一封装了不同平台的触觉反馈接口,无需关注硬件驱动差异; 支持从游戏手柄/鼠标直接打开触觉设备,简化设备管理; 提供简易震动和复杂力反馈两套API,兼顾易用性和灵活性; 使用建议: 优先使用简易震动(Rumble)实现基础震动效果,兼容性最好; 复杂力反馈效果需先检查设备支持性,避免运行时错误; 效果播放完成后及时销毁,避免占用设备资源; 关键接口: 设备管理:SDL_GetHaptics/SDL_OpenHaptic/SDL_CloseHaptic; 简易震动:SDL_HapticRumbleSupported/SDL_InitHapticRumble/SDL_PlayHapticRumble; 复杂效果:SDL_CreateHapticEffect/SDL_RunHapticEffect/SDL_DestroyHapticEffect。
2026年-3月-6日
2 阅读
0 评论
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_HIDAPI (CategoryHIDAPI)
HIDAPI 子系统(CategoryHIDAPI) SDL HIDAPI 功能的头文件封装模块,适配了 Alan Ott 开发的原生 HIDAPI 接口,其源代码遵循以下许可协议: HIDAPI - 用于与 HID 设备通信的跨平台库 版权所有 2009,Alan Ott,Signal 11 Software。 保留所有权利。 只要源文件中的版权声明保持完整,任何人可出于任何原因使用本软件。 注:该许可协议与 SDL zlib 许可协议的第三条内容一致,不会为使用者增加任何新的约束条件。 若你希望使用不含此模块的 SDL 版本,可在编译时将 SDL_HIDAPI_DISABLED 定义为 1。例如在 iOS 或 tvOS 平台上,关闭该模块可避免依赖 CoreBluetooth 框架。 函数 SDL_hid_ble_scan:扫描蓝牙低功耗(BLE)HID 设备(指定扫描时长,发现可用的 BLE HID 设备) SDL_hid_close:关闭已打开的 HID 设备句柄(释放设备资源,终止与设备的通信) SDL_hid_device_change_count:获取 HID 设备变更计数(用于检测设备的插拔事件) SDL_hid_enumerate:枚举系统中所有 HID 设备(按厂商ID/产品ID筛选,返回设备信息链表) SDL_hid_exit:退出 HIDAPI 子系统(释放全局资源,与 SDL_hid_init 配对使用) SDL_hid_free_enumeration:释放枚举 HID 设备时分配的内存(避免内存泄漏) SDL_hid_get_device_info:获取已打开 HID 设备的详细信息(返回 SDL_hid_device_info 结构体) SDL_hid_get_feature_report:从 HID 设备读取特征报告(用于获取设备配置/状态信息) SDL_hid_get_indexed_string:读取 HID 设备的索引字符串(获取设备的自定义描述字符串) SDL_hid_get_input_report:从 HID 设备读取输入报告(获取设备的实时输入数据) SDL_hid_get_manufacturer_string:读取 HID 设备的厂商名称字符串 SDL_hid_get_product_string:读取 HID 设备的产品名称字符串 SDL_hid_get_properties:获取 HID 设备的属性集合(如支持的报告类型、传输速率等) SDL_hid_get_report_descriptor:获取 HID 设备的报告描述符(解析设备的输入/输出报告格式) SDL_hid_get_serial_number_string:读取 HID 设备的序列号字符串 SDL_hid_init:初始化 HIDAPI 子系统(必须先调用,否则无法使用其他 HIDAPI 函数) SDL_hid_open:通过厂商ID和产品ID打开 HID 设备(支持指定设备序列号区分同型号设备) SDL_hid_open_path:通过设备路径打开 HID 设备(更精准的设备定位方式) SDL_hid_read:从 HID 设备读取数据(阻塞式读取,直到有数据可用) SDL_hid_read_timeout:带超时的 HID 设备数据读取(指定超时时间,避免永久阻塞) SDL_hid_send_feature_report:向 HID 设备发送特征报告(用于配置设备参数) SDL_hid_set_nonblocking:设置 HID 设备的非阻塞模式(影响 read/write 操作的阻塞行为) SDL_hid_write:向 HID 设备写入数据(发送输出报告,控制设备行为) 数据类型 SDL_hid_device:HID 设备句柄类型(标识已打开的 HID 设备,用于后续读写操作) 结构体 SDL_hid_device_info:HID 设备信息结构体(包含厂商ID、产品ID、设备路径、名称、总线类型等) 枚举 SDL_hid_bus_type:HID 设备总线类型枚举(如 USB、蓝牙、I2C、SPI 等) 宏 (无) FreeBASIC 示例代码 ' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库,且启用 HIDAPI) #Include "SDL.bi" ' 定义常用常量(示例:通用 USB HID 设备的厂商/产品ID,可替换为实际设备值) Const VENDOR_ID As UShort = &H1234 ' 厂商ID(示例值,需替换为实际设备ID) Const PRODUCT_ID As UShort = &H5678 ' 产品ID(示例值,需替换为实际设备ID) Const REPORT_SIZE As Integer = 64 ' HID 报告长度(根据设备协议调整) ' 枚举并打印所有 HID 设备信息 Sub EnumerateAndPrintHIDDevices() ' 枚举所有 HID 设备(vendor_id=0, product_id=0 表示枚举全部) Dim As SDL_hid_device_info Ptr devs = SDL_hid_enumerate(0, 0) If (devs = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "枚举 HID 设备失败:%s", SDL_GetError()) Return End If SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 系统中所有 HID 设备 ====") ' 遍历设备链表 Dim As SDL_hid_device_info Ptr dev = devs Dim As Integer devCount = 0 While (dev <> NULL) devCount += 1 ' 获取总线类型描述 Dim As String busType = "" Select Case dev->bus_type Case SDL_HID_BUS_USB: busType = "USB" Case SDL_HID_BUS_BLUETOOTH: busType = "蓝牙(BLE)" Case SDL_HID_BUS_I2C: busType = "I2C" Case SDL_HID_BUS_SPI: busType = "SPI" Case Else: busType = "未知总线" End Select ' 打印设备信息 SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "设备 %d:", devCount) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, " 厂商ID: 0x%04X", dev->vendor_id) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, " 产品ID: 0x%04X", dev->product_id) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, " 设备路径: %s", dev->path) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, " 厂商名称: %s", IIf(dev->manufacturer_string, dev->manufacturer_string, "未知")) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, " 产品名称: %s", IIf(dev->product_string, dev->product_string, "未知")) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, " 序列号: %s", IIf(dev->serial_number, dev->serial_number, "无")) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, " 总线类型: %s", busType) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "------------------------") dev = dev->next Wend SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "共发现 %d 个 HID 设备", devCount) ' 释放枚举内存 SDL_hid_free_enumeration(devs) End Sub ' 打开指定 HID 设备并测试读写 Function OpenAndTestHIDDevice(ByVal vendorID As UShort, ByVal productID As UShort) As Boolean ' 初始化 HIDAPI Dim As Integer initResult = SDL_hid_init() If (initResult < 0) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "初始化 HIDAPI 失败:%s", SDL_GetError()) Return False End If ' 打开 HID 设备 SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "尝试打开 HID 设备 (VID:0x%04X, PID:0x%04X)...", vendorID, productID) Dim As SDL_hid_device Ptr dev = SDL_hid_open(vendorID, productID, NULL) If (dev = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "打开 HID 设备失败:%s", SDL_GetError()) SDL_hid_exit() Return False End If SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "成功打开 HID 设备") ' 获取设备信息 Dim As ZString * 256 manufacturer, product, serial If (SDL_hid_get_manufacturer_string(dev, @manufacturer, 256) = 0) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "厂商名称:%s", manufacturer) End If If (SDL_hid_get_product_string(dev, @product, 256) = 0) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "产品名称:%s", product) End If If (SDL_hid_get_serial_number_string(dev, @serial, 256) = 0) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "序列号:%s", serial) End If ' 设置非阻塞模式 SDL_hid_set_nonblocking(dev, 1) ' 测试:发送输出报告(示例数据,需根据设备协议调整) Dim As UByte outputReport(0 To REPORT_SIZE-1) = {0} outputReport(0) = 0x01 ' 报告ID(根据设备协议) outputReport(1) = 0x02 ' 数据1 outputReport(2) = 0x03 ' 数据2 Dim As Integer writeResult = SDL_hid_write(dev, @outputReport(0), REPORT_SIZE) If (writeResult < 0) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "写入 HID 设备失败:%s", SDL_GetError()) Else SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "成功写入 %d 字节到 HID 设备", writeResult) End If ' 测试:读取输入报告(超时 1000ms) Dim As UByte inputReport(0 To REPORT_SIZE-1) = {0} Dim As Integer readResult = SDL_hid_read_timeout(dev, @inputReport(0), REPORT_SIZE, 1000) If (readResult < 0) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "读取 HID 设备失败:%s", SDL_GetError()) ElseIf (readResult = 0) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "读取超时(1000ms),无数据返回") Else SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "成功读取 %d 字节:", readResult) ' 打印读取到的数据(十六进制) Dim As String hexStr = "" For i As Integer = 0 To readResult-1 hexStr += SDL_Format("0x%02X ", inputReport(i)) Next SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, " 数据:%s", hexStr) End If ' 关闭设备 SDL_hid_close(dev) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "已关闭 HID 设备") ' 退出 HIDAPI SDL_hid_exit() Return True End Function ' 主程序 Sub Main() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL HIDAPI 示例程序 ===") ' 1. 枚举并打印所有 HID 设备 EnumerateAndPrintHIDDevices() ' 2. 尝试打开指定 HID 设备(替换为实际设备的 VID/PID) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, vbCrLf & "==== 测试指定 HID 设备 ====") Dim As Boolean testResult = OpenAndTestHIDDevice(VENDOR_ID, PRODUCT_ID) If (testResult) Then SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "HID 设备测试成功") Else SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "HID 设备测试失败(请检查 VID/PID 是否正确)") End If SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序结束") End Sub ' 运行主程序 Main() ' 辅助函数:格式化字符串(模拟 SDL 格式化输出) Function SDL_Format(ByVal fmt As String, ...) As String Dim As Any Ptr args = va_start() Dim As ZString * 1024 buffer vsprintf(@buffer, fmt, args) va_end(args) Return buffer End Function 核心知识点补充 HID 设备基础概念: VID/PID:厂商ID(Vendor ID)和产品ID(Product ID)是识别 USB/HID 设备的唯一标识,由 USB-IF 分配; 报告(Report):HID 设备通过报告传输数据,分为输入报告(设备→主机)、输出报告(主机→设备)、特征报告(配置/状态); 报告描述符:定义设备支持的报告格式、数据长度、用途,是解析 HID 数据的关键。 开发注意事项: 权限问题:Linux/macOS 平台访问 HID 设备可能需要 root 权限或自定义 udev 规则; 跨平台兼容:Windows 需注意设备路径格式,macOS/iOS 需处理蓝牙 HID 设备的扫描逻辑; 非阻塞模式:设置非阻塞模式后,SDL_hid_read 会立即返回,避免主线程阻塞。 调试技巧: 使用 SDL_hid_enumerate(0, 0) 枚举所有设备,确认目标设备的 VID/PID 和路径; 通过 SDL_hid_get_report_descriptor 获取报告描述符,解析设备的数据格式; 移动平台(Android/iOS)需配置权限(如蓝牙权限)才能访问 BLE HID 设备。 总结 核心优势: SDL HIDAPI 封装了原生 HIDAPI,提供跨平台的 HID 设备访问接口,无需关注底层系统差异; 支持 USB、蓝牙等多种总线类型的 HID 设备,涵盖键鼠、手柄、传感器、自定义 HID 设备; 许可协议宽松,可自由使用且无额外约束; 使用建议: 必须先调用 SDL_hid_init 初始化,使用完调用 SDL_hid_exit 释放资源; 枚举设备后务必调用 SDL_hid_free_enumeration 释放内存,避免泄漏; 实际开发中需根据设备的 HID 协议解析读写数据,示例中的报告格式仅为参考; 关键接口: 设备枚举:SDL_hid_enumerate/SDL_hid_free_enumeration; 设备操作:SDL_hid_open/SDL_hid_close/SDL_hid_init/SDL_hid_exit; 数据读写:SDL_hid_read/SDL_hid_write/SDL_hid_read_timeout。
2026年-3月-6日
3 阅读
0 评论
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_传感器(CategorySensor)
传感器子系统(CategorySensor) SDL 提供的传感器管理模块,可访问不同平台设备上的陀螺仪、加速度计等传感器硬件,获取设备的运动、姿态等物理数据。 基础使用前提 调用 SDL_Init() 初始化时必须传入 SDL_INIT_SENSOR 标志,SDL 会扫描系统中的传感器设备并加载对应的驱动程序,之后才能正常使用传感器相关接口。 函数 SDL_CloseSensor:关闭已打开的传感器设备(释放传感器资源,停止数据采集) SDL_GetSensorData:获取传感器的最新数据(传入数据缓冲区,返回三轴/多轴的物理测量值) SDL_GetSensorFromID:通过传感器ID获取对应的 SDL_Sensor 指针(用于后续操作该传感器) SDL_GetSensorID:获取已打开传感器的唯一标识ID SDL_GetSensorName:获取已打开传感器的设备名称(如 "Gyroscope"、"Accelerometer") SDL_GetSensorNameForID:通过传感器ID获取设备名称 SDL_GetSensorNonPortableType:获取传感器的非可移植类型标识(平台相关的硬件类型编码) SDL_GetSensorNonPortableTypeForID:通过传感器ID获取非可移植类型标识 SDL_GetSensorProperties:获取传感器的属性集合(如数据更新频率、精度等) SDL_GetSensors:枚举系统中所有可用的传感器设备(返回传感器ID列表及数量) SDL_GetSensorType:获取已打开传感器的标准化类型(如陀螺仪、加速度计、磁力计) SDL_GetSensorTypeForID:通过传感器ID获取标准化类型 SDL_OpenSensor:打开指定ID的传感器设备(返回 SDL_Sensor 指针,开始采集数据) SDL_UpdateSensors:强制更新所有传感器的状态(SDL 内部自动调用,一般无需手动执行) 数据类型 SDL_Sensor:传感器设备句柄类型(标识已打开的传感器设备) SDL_SensorID:传感器唯一标识类型(区分系统中的不同传感器设备) 结构体 (无) 枚举 SDL_SensorType:传感器类型枚举(SDL_SENSOR_ACCELEROMETER 加速度计、SDL_SENSOR_GYROSCOPE 陀螺仪、SDL_SENSOR_MAGNETOMETER 磁力计等) 宏 SDL_STANDARD_GRAVITY:标准重力加速度常量(值为 9.80665,用于加速度计数据的单位转换) FreeBASIC 示例代码 ' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库) #Include "SDL.bi" ' 传感器数据存储结构体 Type SensorData accelerometer As Single(0 To 2) ' 加速度计数据(X/Y/Z轴,单位:m/s²) gyroscope As Single(0 To 2) ' 陀螺仪数据(X/Y/Z轴,单位:rad/s) magnetometer As Single(0 To 2) ' 磁力计数据(X/Y/Z轴,单位:μT) accelUpdated As Boolean ' 加速度计数据是否更新 gyroUpdated As Boolean ' 陀螺仪数据是否更新 magUpdated As Boolean ' 磁力计数据是否更新 End Type Dim Shared As SensorData sensorData Dim Shared As SDL_Sensor Ptr accelSensor = NULL Dim Shared As SDL_Sensor Ptr gyroSensor = NULL Dim Shared As SDL_Sensor Ptr magSensor = NULL ' 初始化传感器数据 Sub InitSensorData() sensorData.accelerometer(0) = 0.0 : sensorData.accelerometer(1) = 0.0 : sensorData.accelerometer(2) = 0.0 sensorData.gyroscope(0) = 0.0 : sensorData.gyroscope(1) = 0.0 : sensorData.gyroscope(2) = 0.0 sensorData.magnetometer(0) = 0.0 : sensorData.magnetometer(1) = 0.0 : sensorData.magnetometer(2) = 0.0 sensorData.accelUpdated = False sensorData.gyroUpdated = False sensorData.magUpdated = False End Sub ' 枚举并打开可用传感器 Function OpenAvailableSensors() As Boolean Dim As Integer sensorCount = SDL_GetSensors(0, NULL) If (sensorCount = 0) Then SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "系统未检测到任何传感器设备") Return False End If ' 分配内存存储传感器ID Dim As SDL_SensorID Ptr sensorIDs = Callocate(sensorCount * SizeOf(SDL_SensorID)) If (sensorIDs = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_INPUT, "分配传感器ID内存失败") Return False End If ' 获取所有传感器ID sensorCount = SDL_GetSensors(sensorCount, sensorIDs) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "检测到 %d 个传感器设备", sensorCount) ' 遍历传感器,打开需要的类型 For i As Integer = 0 To sensorCount - 1 Dim As SDL_SensorType type = SDL_GetSensorTypeForID(sensorIDs[i]) Dim As ZString Ptr name = SDL_GetSensorNameForID(sensorIDs[i]) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "传感器 %d:名称=%s,类型=%d", i, name, type) ' 打开对应类型的传感器 Select Case type Case SDL_SENSOR_ACCELEROMETER If (accelSensor = NULL) Then accelSensor = SDL_OpenSensor(sensorIDs[i]) If (accelSensor <> NULL) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "成功打开加速度计传感器") Else SDL_LogError(SDL_LOG_CATEGORY_INPUT, "打开加速度计失败:%s", SDL_GetError()) End If End If Case SDL_SENSOR_GYROSCOPE If (gyroSensor = NULL) Then gyroSensor = SDL_OpenSensor(sensorIDs[i]) If (gyroSensor <> NULL) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "成功打开陀螺仪传感器") Else SDL_LogError(SDL_LOG_CATEGORY_INPUT, "打开陀螺仪失败:%s", SDL_GetError()) End If End If Case SDL_SENSOR_MAGNETOMETER If (magSensor = NULL) Then magSensor = SDL_OpenSensor(sensorIDs[i]) If (magSensor <> NULL) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "成功打开磁力计传感器") Else SDL_LogError(SDL_LOG_CATEGORY_INPUT, "打开磁力计失败:%s", SDL_GetError()) End If End If End Select If (name <> NULL) Then SDL_free(name) Next Deallocate(sensorIDs) ' 检查是否至少打开了一个传感器 Return (accelSensor <> NULL) Or (gyroSensor <> NULL) Or (magSensor <> NULL) End Function ' 读取传感器数据 Sub ReadSensorData() ' 读取加速度计数据(原始值单位为 m/s²,已包含重力) If (accelSensor <> NULL) Then Dim As Single data(0 To 2) If (SDL_GetSensorData(accelSensor, @data[0], 3, 0) = 0) Then sensorData.accelerometer(0) = data(0) sensorData.accelerometer(1) = data(1) sensorData.accelerometer(2) = data(2) sensorData.accelUpdated = True End If End If ' 读取陀螺仪数据(原始值单位为 rad/s) If (gyroSensor <> NULL) Then Dim As Single data(0 To 2) If (SDL_GetSensorData(gyroSensor, @data[0], 3, 0) = 0) Then sensorData.gyroscope(0) = data(0) sensorData.gyroscope(1) = data(1) sensorData.gyroscope(2) = data(2) sensorData.gyroUpdated = True End If End If ' 读取磁力计数据(原始值单位为 μT) If (magSensor <> NULL) Then Dim As Single data(0 To 2) If (SDL_GetSensorData(magSensor, @data[0], 3, 0) = 0) Then sensorData.magnetometer(0) = data(0) sensorData.magnetometer(1) = data(1) sensorData.magnetometer(2) = data(2) sensorData.magUpdated = True End If End If End Sub ' 打印传感器数据(每秒打印一次) Sub PrintSensorData(ByVal frameCount As Integer) Static As Integer lastPrintFrame = 0 If (frameCount - lastPrintFrame < 60) Then Return ' 约60FPS,每秒打印一次 lastPrintFrame = frameCount SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 传感器数据 ====") If (sensorData.accelUpdated) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "加速度计 (m/s²):X=%.2f, Y=%.2f, Z=%.2f | 重力分量:%.2fG", _ sensorData.accelerometer(0), sensorData.accelerometer(1), sensorData.accelerometer(2), _ Sqr(sensorData.accelerometer(0)^2 + sensorData.accelerometer(1)^2 + sensorData.accelerometer(2)^2) / SDL_STANDARD_GRAVITY) sensorData.accelUpdated = False End If If (sensorData.gyroUpdated) Then ' 转换为度/秒(rad/s × (180/π)) Dim As Single gyroX = sensorData.gyroscope(0) * (180 / 3.14159265) Dim As Single gyroY = sensorData.gyroscope(1) * (180 / 3.14159265) Dim As Single gyroZ = sensorData.gyroscope(2) * (180 / 3.14159265) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "陀螺仪 (°/s):X=%.2f, Y=%.2f, Z=%.2f", gyroX, gyroY, gyroZ) sensorData.gyroUpdated = False End If If (sensorData.magUpdated) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "磁力计 (μT):X=%.2f, Y=%.2f, Z=%.2f | 磁场强度:%.2fμT", _ sensorData.magnetometer(0), sensorData.magnetometer(1), sensorData.magnetometer(2), _ Sqr(sensorData.magnetometer(0)^2 + sensorData.magnetometer(1)^2 + sensorData.magnetometer(2)^2)) sensorData.magUpdated = False End If End Sub ' 主程序 Dim As SDL_Window Ptr window = NULL Dim As SDL_Event evt Dim As Boolean quit = False Dim As Integer frameCount = 0 ' 1. 初始化 SDL(必须包含 SENSOR 标志) If (SDL_Init(SDL_INIT_VIDEO Or SDL_INIT_SENSOR) < 0) Then SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError()) End 1 End If ' 2. 创建窗口 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 ' 3. 初始化传感器数据并打开可用传感器 InitSensorData() If (Not OpenAvailableSensors()) Then SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "未打开任何传感器,程序仍会运行但无数据输出") End If SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL 传感器示例 ===") SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "提示:移动设备查看传感器数据变化 | ESC:退出") ' 4. 主循环 While (Not quit) frameCount += 1 ' 处理事件队列 While (SDL_PollEvent(@evt)) Select Case evt.type Case SDL_QUITEVENT quit = True Case SDL_EVENT_KEY_DOWN If (evt.key.scancode = SDL_SCANCODE_ESCAPE) Then SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下 ESC,退出程序") quit = True End If End Select Wend ' 读取传感器数据 ReadSensorData() ' 定期打印传感器数据 PrintSensorData(frameCount) SDL_Delay(16) ' 约60FPS Wend ' 5. 清理资源 If (accelSensor <> NULL) Then SDL_CloseSensor(accelSensor) If (gyroSensor <> NULL) Then SDL_CloseSensor(gyroSensor) If (magSensor <> NULL) Then SDL_CloseSensor(magSensor) SDL_DestroyWindow(window) SDL_Quit() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出") End 0 核心知识点补充 传感器数据单位说明: 传感器类型 原始数据单位 常用转换 说明 加速度计 m/s² ÷9.80665 转换为重力加速度(G) 陀螺仪 rad/s ×(180/π) 转换为度/秒(°/s) 磁力计 μT(微特斯拉) - 直接表示磁场强度 数据处理注意事项: 加速度计数据包含重力分量(静止时Z轴约为9.8 m/s²),需通过滤波算法分离运动加速度和重力; 陀螺仪数据存在漂移,需结合加速度计/磁力计使用卡尔曼滤波、互补滤波等算法校准; 传感器数据更新频率较高(通常 50~200Hz),建议在独立线程中读取,避免阻塞主线程。 跨平台支持: 移动平台(Android/iOS)全面支持加速度计、陀螺仪、磁力计; 桌面平台(Windows/macOS)仅部分设备(如笔记本、游戏手柄)支持传感器; 游戏手柄的传感器可通过 SDL_GamepadHasSensor/SDL_GetGamepadSensorData 接口访问。 总结 核心特性: SDL 统一封装了不同平台的传感器接口,无需关注底层驱动和数据格式差异; 支持加速度计、陀螺仪、磁力计等主流传感器类型,数据单位标准化; SDL_STANDARD_GRAVITY 宏提供标准重力加速度,方便单位转换; 使用建议: 初始化时必须传入 SDL_INIT_SENSOR 标志,否则传感器接口无法使用; 读取数据前需检查传感器是否成功打开,避免空指针访问; 传感器数据需做滤波/校准处理,提升数据稳定性和准确性; 关键接口: 设备枚举:SDL_GetSensors 获取所有传感器ID; 设备操作:SDL_OpenSensor/SDL_CloseSensor 打开/关闭传感器; 数据读取:SDL_GetSensorData 获取最新传感器数据。
2026年-3月-6日
3 阅读
0 评论
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_手写笔(CategoryPen)
手写笔子系统(CategoryPen) SDL 提供的压感手写笔(触控笔/橡皮擦)事件处理模块,适用于绘图板、配备手写笔的移动设备/平板等场景的精准输入处理。 核心事件类型 要处理手写笔输入,只需监听以下核心事件: SDL_EVENT_PEN_PROXIMITY_IN/SDL_EVENT_PEN_PROXIMITY_OUT:手写笔进入/离开感应范围事件(SDL_PenProximityEvent); SDL_EVENT_PEN_DOWN/SDL_EVENT_PEN_UP:手写笔按下/抬起事件(SDL_PenTouchEvent); SDL_EVENT_PEN_MOTION:手写笔移动事件(SDL_PenMotionEvent); SDL_EVENT_PEN_BUTTON_DOWN/SDL_EVENT_PEN_BUTTON_UP:手写笔按键按下/抬起事件(SDL_PenButtonEvent); SDL_EVENT_PEN_AXIS:手写笔轴数据变化事件(SDL_PenAxisEvent)。 手写笔高级特性 手写笔不仅支持基础的触摸输入,还可提供压力、倾斜角度、旋转角度等额外轴数据,满足精细绘图、书写等场景需求。 手写笔ID规则 手写笔开始输入时,SDL 会分配唯一的 SDL_PenID,只要设备保持连接,该ID在进程生命周期内保持不变; 手写笔离开感应范围(如远离数位板)后重新进入,会触发接近事件,但 SDL_PenID 仍保持一致; 数位板拔插后重新连接,SDL 可能无法识别为同一硬件,新输入会分配新的 SDL_PenID。 跨平台注意事项 不同平台对手写笔的支持程度差异极大: 部分平台支持多设备同时工作,部分平台会将所有连接的手写笔视为单个逻辑设备(类似多USB鼠标控制同一系统光标); 部分平台不支持手写笔按键、距离轴等功能; 极少平台能提前报告手写笔支持的功能,最佳实践是:要么提供UI让用户配置手写笔,要么在首次收到事件时动态适配新功能; 若手写笔硬件支持某功能但SDL未响应,大概率是操作系统层面的支持问题。 函数 SDL_GetPenDeviceType:获取手写笔关联设备的类型(如数位板、触摸屏、平板等) 数据类型 SDL_PenID:手写笔唯一标识类型(区分不同手写笔设备/实例) SDL_PenInputFlags:手写笔输入标志类型(位掩码,标识输入特性:如橡皮擦模式、桶按钮按下等) 结构体 SDL_PenAxisEvent:手写笔轴事件结构体(包含轴类型、轴数值、手写笔ID等) SDL_PenButtonEvent:手写笔按键事件结构体(包含按键ID、按下/抬起状态、手写笔ID等) SDL_PenMotionEvent:手写笔移动事件结构体(包含位置、压力、倾斜角度、手写笔ID等) SDL_PenProximityEvent:手写笔接近事件结构体(包含进入/离开状态、手写笔ID、设备类型等) SDL_PenTouchEvent:手写笔触摸事件结构体(包含按下/抬起状态、位置、压力、手写笔ID等) 枚举 SDL_PenAxis:手写笔轴类型枚举(如压力轴、X/Y倾斜轴、旋转轴、距离轴等) SDL_PenDeviceType:手写笔关联设备类型枚举(如数位板、触摸屏、平板、未知设备等) 宏 SDL_PEN_MOUSEID:手写笔模拟鼠标事件的设备ID(用于区分原生鼠标和手写笔模拟鼠标) SDL_PEN_TOUCHID:手写笔关联的触摸设备ID标识(用于关联手写笔事件到触摸设备) FreeBASIC 示例代码 ' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库) #Include "SDL.bi" ' 绘图状态结构体 Type DrawingState isDrawing As Boolean ' 是否正在绘图 lastX As Integer ' 上一帧X坐标 lastY As Integer ' 上一帧Y坐标 penID As SDL_PenID ' 当前手写笔ID pressure As Single ' 压感值(0.0~1.0) tiltX As Single ' X轴倾斜角度(-1.0~1.0) tiltY As Single ' Y轴倾斜角度(-1.0~1.0) End Type Dim Shared As DrawingState drawState ' 初始化绘图状态 Sub InitDrawingState() drawState.isDrawing = False drawState.lastX = 0 drawState.lastY = 0 drawState.penID = 0 drawState.pressure = 0.0 drawState.tiltX = 0.0 drawState.tiltY = 0.0 End Sub ' 绘制手写笔轨迹 Sub DrawPenStroke(ByVal renderer As SDL_Renderer Ptr, ByVal x As Integer, ByVal y As Integer) If (Not drawState.isDrawing) Then Return ' 根据压感值调整线条宽度(1~20像素) Dim As Integer lineWidth = 1 + Int(drawState.pressure * 19) ' 根据倾斜角度调整颜色(倾斜越大,颜色越浅) Dim As UByte alpha = 255 - Int((Abs(drawState.tiltX) + Abs(drawState.tiltY)) * 128) SDL_SetRenderDrawColor(renderer, 0, 150, 255, alpha) ' 设置线条宽度(SDL3 支持渲染器线条宽度) SDL_SetRenderDrawLineWidth(renderer, lineWidth) ' 绘制线条(从上一位置到当前位置) SDL_RenderDrawLine(renderer, drawState.lastX, drawState.lastY, x, y) ' 更新上一位置 drawState.lastX = x drawState.lastY = y End Sub ' 打印手写笔事件信息 Sub PrintPenEventInfo(ByRef evt As SDL_Event) Select Case evt.type Case SDL_EVENT_PEN_PROXIMITY_IN SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "手写笔进入感应范围 | PenID:%llu | 设备类型:%d", _ evt.penproximity.penid, evt.penproximity.devicetype) Case SDL_EVENT_PEN_PROXIMITY_OUT SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "手写笔离开感应范围 | PenID:%llu", evt.penproximity.penid) Case SDL_EVENT_PEN_DOWN SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "手写笔按下 | PenID:%llu | 位置:(%d, %d) | 压力:%.2f", _ evt.pentouch.penid, evt.pentouch.x, evt.pentouch.y, evt.pentouch.pressure) Case SDL_EVENT_PEN_UP SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "手写笔抬起 | PenID:%llu | 位置:(%d, %d)", _ evt.pentouch.penid, evt.pentouch.x, evt.pentouch.y) Case SDL_EVENT_PEN_MOTION SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "手写笔移动 | PenID:%llu | 位置:(%d, %d) | 压力:%.2f | 倾斜(X/Y):(%.2f, %.2f)", _ evt.penmotion.penid, evt.penmotion.x, evt.penmotion.y, _ evt.penmotion.pressure, evt.penmotion.tiltx, evt.penmotion.tilty) Case SDL_EVENT_PEN_BUTTON_DOWN SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "手写笔按键按下 | PenID:%llu | 按键ID:%d", _ evt.penbutton.penid, evt.penbutton.button) Case SDL_EVENT_PEN_BUTTON_UP SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "手写笔按键抬起 | PenID:%llu | 按键ID:%d", _ evt.penbutton.penid, evt.penbutton.button) Case SDL_EVENT_PEN_AXIS Dim As ZString Ptr axisName = SDL_GetStringForPenAxis(evt.penaxis.axis) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "手写笔轴变化 | PenID:%llu | 轴类型:%s | 数值:%.2f", _ evt.penaxis.penid, axisName, evt.penaxis.value) If (axisName <> NULL) Then SDL_free(axisName) End Select End Sub ' 主程序 Dim As SDL_Window Ptr window = NULL Dim As SDL_Renderer Ptr renderer = 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 手写笔绘图示例", _ SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _ 1280, 720, SDL_WINDOW_SHOWN Or SDL_WINDOW_RESIZABLE) renderer = SDL_CreateRenderer(window, -1, _ SDL_RENDERER_ACCELERATED Or SDL_RENDERER_PRESENTVSYNC Or SDL_RENDERER_TARGETTEXTURE) If (window = NULL Or renderer = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建窗口/渲染器失败:%s", SDL_GetError()) SDL_Quit() End 1 End If ' 初始化绘图状态 InitDrawingState() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL 手写笔绘图示例 ===") SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "手写笔操作:绘图(压感/倾斜生效) | 按键:测试笔侧键 | ESC:退出") SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "提示:若无手写笔硬件,可测试鼠标模拟输入(区分原生/手写笔模拟)") ' 3. 主循环 While (Not quit) ' 清屏(白色背景,模拟画纸) SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255) SDL_RenderClear(renderer) ' 处理事件队列 While (SDL_PollEvent(@evt)) Select Case evt.type Case SDL_QUITEVENT quit = True ' 键盘事件 Case SDL_EVENT_KEY_DOWN If (evt.key.scancode = SDL_SCANCODE_ESCAPE) Then SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下 ESC,退出程序") quit = True ElseIf (evt.key.scancode = SDL_SCANCODE_C) Then ' 按C键清空画布 InitDrawingState() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "清空画布") End If ' 手写笔核心事件 Case SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_PROXIMITY_OUT, _ SDL_EVENT_PEN_DOWN, SDL_EVENT_PEN_UP, SDL_EVENT_PEN_MOTION, _ SDL_EVENT_PEN_BUTTON_DOWN, SDL_EVENT_PEN_BUTTON_UP, SDL_EVENT_PEN_AXIS ' 打印事件信息 PrintPenEventInfo(evt) ' 处理绘图逻辑 Select Case evt.type Case SDL_EVENT_PEN_DOWN drawState.isDrawing = True drawState.penID = evt.pentouch.penid drawState.lastX = evt.pentouch.x drawState.lastY = evt.pentouch.y drawState.pressure = evt.pentouch.pressure Case SDL_EVENT_PEN_UP drawState.isDrawing = False Case SDL_EVENT_PEN_MOTION drawState.pressure = evt.penmotion.pressure drawState.tiltX = evt.penmotion.tiltx drawState.tiltY = evt.penmotion.tilty DrawPenStroke(renderer, evt.penmotion.x, evt.penmotion.y) Case SDL_EVENT_PEN_AXIS ' 处理轴事件(如压力轴变化) If (evt.penaxis.axis = SDL_PEN_AXIS_PRESSURE) Then drawState.pressure = evt.penaxis.value ElseIf (evt.penaxis.axis = SDL_PEN_AXIS_TILT_X) Then drawState.tiltX = evt.penaxis.value ElseIf (evt.penaxis.axis = SDL_PEN_AXIS_TILT_Y) Then drawState.tiltY = evt.penaxis.value End If End Select ' 区分原生鼠标和手写笔模拟鼠标 Case SDL_EVENT_MOUSE_BUTTON_DOWN If (evt.button.which = SDL_PEN_MOUSEID) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标点击(手写笔模拟):位置(%d, %d)", evt.button.x, evt.button.y) ' 模拟手写笔按下 drawState.isDrawing = True drawState.lastX = evt.button.x drawState.lastY = evt.button.y drawState.pressure = 0.5 ' 默认压力值 Else SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标点击(原生):位置(%d, %d)", evt.button.x, evt.button.y) End If Case SDL_EVENT_MOUSE_BUTTON_UP If (evt.button.which = SDL_PEN_MOUSEID) Then drawState.isDrawing = False End If Case SDL_EVENT_MOUSE_MOTION If (evt.motion.state And SDL_BUTTON_LMASK) Then If (evt.motion.which = SDL_PEN_MOUSEID) Then ' 模拟手写笔移动 drawState.pressure = 0.5 + (Sin(SDL_GetTicks() * 0.001) * 0.4) ' 模拟压力变化 DrawPenStroke(renderer, evt.motion.x, evt.motion.y) End If End If End Select Wend ' 刷新画面 SDL_RenderPresent(renderer) SDL_Delay(16) ' 约60FPS Wend ' 4. 清理资源 SDL_DestroyRenderer(renderer) SDL_DestroyWindow(window) SDL_Quit() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出") End 0 ' 辅助函数:将手写笔轴枚举转换为可读字符串(SDL3 内置接口封装) Function SDL_GetStringForPenAxis(ByVal axis As SDL_PenAxis) As ZString Ptr Select Case axis Case SDL_PEN_AXIS_PRESSURE: Return SDL_strdup("压力轴") Case SDL_PEN_AXIS_X: Return SDL_strdup("X轴") Case SDL_PEN_AXIS_Y: Return SDL_strdup("Y轴") Case SDL_PEN_AXIS_TILT_X: Return SDL_strdup("X倾斜轴") Case SDL_PEN_AXIS_TILT_Y: Return SDL_strdup("Y倾斜轴") Case SDL_PEN_AXIS_ROTATION: Return SDL_strdup("旋转轴") Case SDL_PEN_AXIS_DISTANCE: Return SDL_strdup("距离轴") Case Else: Return SDL_strdup("未知轴") End Select End Function 核心知识点补充 手写笔轴类型说明: 轴枚举 取值范围 说明 SDL_PEN_AXIS_PRESSURE 0.0~1.0 压感值(0=无压力,1=最大) SDL_PEN_AXIS_TILT_X -1.0~1.0 X轴倾斜角度(左右倾斜) SDL_PEN_AXIS_TILT_Y -1.0~1.0 Y轴倾斜角度(前后倾斜) SDL_PEN_AXIS_ROTATION 0.0~1.0 旋转角度(笔杆旋转) SDL_PEN_AXIS_DISTANCE 0.0~1.0 与数位板的距离(仅接近事件有效) 压感绘图优化技巧: 压感值映射为线条宽度/透明度,模拟真实画笔效果; 倾斜角度可调整线条颜色/纹理方向,提升绘图真实感; 处理手写笔移动事件时,需插值补点避免线条断裂(高速移动时)。 模拟输入适配: 无手写笔硬件时,可通过 SDL_PEN_MOUSEID 识别手写笔模拟的鼠标事件; 示例中模拟了鼠标输入的压感变化,便于无硬件时测试绘图逻辑; 移动平台上,手写笔事件会自动转换为触摸/鼠标事件,无需额外适配。 总结 核心优势: SDL 统一封装了不同平台的手写笔事件,无需关注底层驱动差异; 支持压感、倾斜、旋转等高级特性,满足专业绘图场景需求; 通过 SDL_PEN_MOUSEID 可区分原生鼠标和手写笔模拟输入; 使用建议: 优先监听 SDL_EVENT_PEN_* 系列事件处理精准手写笔输入; 针对不同平台的功能差异做兼容处理,避免依赖特定平台的特性; 无手写笔硬件时,可通过鼠标模拟输入验证核心逻辑; 关键接口: 事件处理:SDL_EVENT_PEN_DOWN/UP/MOTION 等核心事件; 设备信息:SDL_GetPenDeviceType 获取设备类型; 输入区分:SDL_PEN_MOUSEID 宏区分模拟/原生输入。
2026年-3月-6日
4 阅读
0 评论
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_触摸输入(CategoryTouch)
触摸输入子系统(CategoryTouch) SDL 在支持触摸功能的平台上提供了完整的触摸输入处理能力,可管理多个触摸设备,并追踪每个设备上的多点触控(多手指触摸)操作。 核心交互方式 触摸操作主要通过事件系统传递: SDL_EVENT_FINGER_DOWN:手指按下事件; SDL_EVENT_FINGER_MOTION:手指移动事件; SDL_EVENT_FINGER_UP:手指抬起事件。 同时也提供了查询触摸硬件信息的辅助函数。 虚拟鼠标事件 SDL 触摸系统默认会将触摸操作转换为虚拟鼠标事件,这一特性可让桌面应用无需大幅修改代码就能在触屏手机上运行。 若应用需要区分原生鼠标输入和触摸模拟的鼠标输入,需忽略 which 字段为 SDL_TOUCH_MOUSEID 的鼠标事件; 反之,若只需基础交互,可直接使用鼠标事件处理逻辑,无需额外适配触摸。 函数 SDL_GetTouchDeviceName:根据触摸设备ID获取设备名称(如 "Touchscreen") SDL_GetTouchDevices:枚举系统中所有已连接的触摸设备(返回触摸设备ID列表及数量) SDL_GetTouchDeviceType:获取触摸设备的类型(如触摸屏、触摸板、手写板) SDL_GetTouchFingers:获取指定触摸设备上所有活动的手指信息(返回手指ID列表及数量) 数据类型 SDL_FingerID:手指ID类型(标识触摸设备上的单个手指,多点触控时区分不同手指) SDL_TouchID:触摸设备ID类型(标识系统中的单个触摸设备) 结构体 SDL_Finger:手指状态结构体(包含手指ID、归一化的触摸位置(x/y:0.0~1.0)、压力值(0.0~1.0)) 枚举 SDL_TouchDeviceType:触摸设备类型枚举(SDL_TOUCH_DEVICE_TYPE_DIRECT 触摸屏(直接触摸)、SDL_TOUCH_DEVICE_TYPE_INDIRECT_ABS 绝对坐标触摸板、SDL_TOUCH_DEVICE_TYPE_INDIRECT_REL 相对坐标触摸板) 宏 SDL_MOUSE_TOUCHID:鼠标事件关联的触摸设备ID标识(用于反向关联鼠标事件到触摸设备) SDL_TOUCH_MOUSEID:触摸模拟鼠标事件的设备ID(用于区分原生鼠标和触摸模拟鼠标) FreeBASIC 示例代码 ' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库) #Include "SDL.bi" ' 辅助函数:打印触摸设备信息 Sub PrintTouchDeviceInfo(ByVal touchID As SDL_TouchID) Dim As ZString Ptr devName = SDL_GetTouchDeviceName(touchID) Dim As SDL_TouchDeviceType devType = SDL_GetTouchDeviceType(touchID) Dim As String typeStr = "" Select Case devType Case SDL_TOUCH_DEVICE_TYPE_DIRECT: typeStr = "触摸屏(直接触摸)" Case SDL_TOUCH_DEVICE_TYPE_INDIRECT_ABS: typeStr = "触摸板(绝对坐标)" Case SDL_TOUCH_DEVICE_TYPE_INDIRECT_REL: typeStr = "触摸板(相对坐标)" Case Else: typeStr = "未知设备类型" End Select SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 触摸设备信息 ====") SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "设备ID:%llu | 名称:%s | 类型:%s", touchID, devName, typeStr) ' 获取设备上的活动手指数量 Dim As Integer fingerCount = SDL_GetTouchFingers(touchID, NULL, 0) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "当前活动手指数量:%d", fingerCount) If (devName <> NULL) Then SDL_free(devName) End Sub ' 主程序 Dim As SDL_Window Ptr window = NULL Dim As SDL_Renderer Ptr renderer = NULL Dim As SDL_TouchID Ptr touchIDs = NULL Dim As Integer touchCount = 0 Dim As SDL_Event evt Dim As Boolean quit = False ' 存储触摸点状态(最多支持5点触控) Type TouchPoint active As Boolean x As Double y As Double pressure As Double End Type Dim As TouchPoint touchPoints(0 To 4) ' 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 触摸输入示例", _ SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _ 800, 600, SDL_WINDOW_SHOWN Or SDL_WINDOW_RESIZABLE) renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED Or SDL_RENDERER_PRESENTVSYNC) If (window = NULL Or renderer = NULL) Then SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建窗口/渲染器失败:%s", SDL_GetError()) SDL_Quit() End 1 End If ' 3. 枚举触摸设备 touchCount = SDL_GetTouchDevices(0, NULL) If (touchCount > 0) Then touchIDs = Callocate(touchCount * SizeOf(SDL_TouchID)) touchCount = SDL_GetTouchDevices(touchCount, touchIDs) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "检测到 %d 个触摸设备", touchCount) ' 打印第一个触摸设备信息 PrintTouchDeviceInfo(touchIDs[0]) Else SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "系统未检测到触摸设备,仍可测试虚拟鼠标事件") End If SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL 触摸输入示例 ===") SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "触摸屏幕:显示触摸点 | 鼠标操作:区分原生/模拟 | ESC:退出") ' 4. 主循环 While (Not quit) ' 清屏(黑色背景) SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255) SDL_RenderClear(renderer) ' 绘制触摸点 For i As Integer = 0 To 4 If (touchPoints(i).active) Then ' 获取窗口尺寸,将归一化坐标转换为像素坐标 Dim As Integer winW, winH SDL_GetWindowSize(window, @winW, @winH) Dim As Integer px = touchPoints(i).x * winW Dim As Integer py = touchPoints(i).y * winH Dim As Integer size = 50 + touchPoints(i).pressure * 50 ' 压力越大,触摸点越大 ' 绘制彩色触摸点(不同手指不同颜色) Select Case i Case 0: SDL_SetRenderDrawColor(renderer, 255, 0, 0, 200) ' 红色 Case 1: SDL_SetRenderDrawColor(renderer, 0, 255, 0, 200) ' 绿色 Case 2: SDL_SetRenderDrawColor(renderer, 0, 0, 255, 200) ' 蓝色 Case 3: SDL_SetRenderDrawColor(renderer, 255, 255, 0, 200) ' 黄色 Case 4: SDL_SetRenderDrawColor(renderer, 255, 0, 255, 200) ' 紫色 End Select ' 绘制圆形触摸点(简化为矩形) Dim As SDL_Rect touchRect = (px - size\2, py - size\2, size, size) SDL_RenderFillRect(renderer, @touchRect) End If Next ' 刷新画面 SDL_RenderPresent(renderer) ' 处理事件队列 While (SDL_PollEvent(@evt)) Select Case evt.type Case SDL_QUITEVENT quit = True ' 键盘事件 Case SDL_EVENT_KEY_DOWN If (evt.key.scancode = SDL_SCANCODE_ESCAPE) Then SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下 ESC,退出程序") quit = True End If ' 触摸事件处理 Case SDL_EVENT_FINGER_DOWN SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "手指按下 | 设备ID:%llu | 手指ID:%llu | 位置:(%.2f, %.2f) | 压力:%.2f", _ evt.tfinger.touchId, evt.tfinger.fingerId, _ evt.tfinger.x, evt.tfinger.y, evt.tfinger.pressure) ' 记录触摸点状态(简化:按手指ID索引存储) Dim As Integer idx = evt.tfinger.fingerId Mod 5 touchPoints(idx).active = True touchPoints(idx).x = evt.tfinger.x touchPoints(idx).y = evt.tfinger.y touchPoints(idx).pressure = evt.tfinger.pressure Case SDL_EVENT_FINGER_MOTION SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "手指移动 | 设备ID:%llu | 手指ID:%llu | 位置:(%.2f, %.2f) | 相对偏移:(%.4f, %.4f)", _ evt.tfinger.touchId, evt.tfinger.fingerId, _ evt.tfinger.x, evt.tfinger.y, evt.tfinger.dx, evt.tfinger.dy) ' 更新触摸点位置 Dim As Integer idx = evt.tfinger.fingerId Mod 5 touchPoints(idx).x = evt.tfinger.x touchPoints(idx).y = evt.tfinger.y touchPoints(idx).pressure = evt.tfinger.pressure Case SDL_EVENT_FINGER_UP SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "手指抬起 | 设备ID:%llu | 手指ID:%llu | 位置:(%.2f, %.2f)", _ evt.tfinger.touchId, evt.tfinger.fingerId, evt.tfinger.x, evt.tfinger.y) ' 清除触摸点状态 Dim As Integer idx = evt.tfinger.fingerId Mod 5 touchPoints(idx).active = False ' 区分原生鼠标和触摸模拟鼠标 Case SDL_EVENT_MOUSE_BUTTON_DOWN If (evt.button.which = SDL_TOUCH_MOUSEID) Then SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标点击(触摸模拟):位置(%d, %d)", evt.button.x, evt.button.y) Else SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标点击(原生):位置(%d, %d)", evt.button.x, evt.button.y) End If End Select Wend SDL_Delay(16) ' 约60FPS Wend ' 5. 清理资源 If (touchIDs <> NULL) Then Deallocate(touchIDs) End If SDL_DestroyRenderer(renderer) SDL_DestroyWindow(window) SDL_Quit() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出") End 0 核心知识点补充 触摸坐标归一化: SDL 触摸事件中的 x/y 坐标为归一化值(0.0 ~ 1.0),对应触摸设备的左上角到右下角; 需转换为窗口像素坐标时,公式:像素坐标 = 归一化坐标 × 窗口尺寸; 压力值(pressure)同样为 0.0 ~ 1.0,仅部分触摸设备支持(如电容屏)。 多点触控处理: 每个手指有唯一的 SDL_FingerID,可通过该ID区分不同手指的操作; 示例中简化为最多支持5点触控,实际可根据需求扩展; 常见多点触控手势(缩放、旋转)需通过多个手指的位置变化计算实现。 跨平台注意事项: 桌面平台(Windows/macOS)的触摸板会被识别为 SDL_TOUCH_DEVICE_TYPE_INDIRECT_ABS; 移动平台(Android/iOS)的触摸屏会被识别为 SDL_TOUCH_DEVICE_TYPE_DIRECT; 虚拟鼠标事件在移动平台默认启用,桌面平台可通过 SDL 提示关闭。 总结 核心特性: SDL 触摸系统提供标准化的多点触控事件,无需关注底层平台差异; 虚拟鼠标事件机制大幅降低跨平台适配成本; 通过 SDL_TOUCH_MOUSEID 可灵活区分原生鼠标和触摸模拟输入; 使用场景: 简单适配:直接使用鼠标事件处理触摸输入; 精细控制:监听 SDL_EVENT_FINGER_* 事件,处理多点触控、压力感应等高级功能; 关键接口: 设备枚举:SDL_GetTouchDevices/SDL_GetTouchDeviceName; 事件处理:SDL_EVENT_FINGER_DOWN/MOTION/UP; 输入区分:SDL_TOUCH_MOUSEID 宏。
2026年-3月-6日
3 阅读
0 评论
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_游戏手柄(CategoryGamepad)
游戏手柄子系统(CategoryGamepad) SDL 提供了底层的摇杆 API,该 API 仅将摇杆视为一堆无规则的按键、轴和方向键。如果你计划自行开发控制配置界面,这个底层 API 能提供极高的灵活性,但开发成本也很高——而如今我们所说的「摇杆」大多是主机风格的标准化游戏手柄。因此 SDL 在底层摇杆功能之上,封装了更易用的游戏手柄(Gamepad)API。 摇杆 vs 游戏手柄核心区别 摇杆(Joystick):用「按键 3」「轴 2」这类无意义的数字标识输入; 游戏手柄(Gamepad):用标准化的位置标识输入(如方向键、肩键、扳机键、A/B/X/Y 键,或 PS 手柄的 X/O/方块/三角键)。 标准化实现原理 SDL 通过「魔术配置字符串」将摇杆转换为标准化游戏手柄——该字符串定义了特定硬件的映射规则(如「检测到该硬件时,按键 2 按下等价于方向键上」)。 SDL 内置了主流控制器的配置,开箱即用; 若设备未被 SDL 识别,用户可通过环境变量添加自定义控制器配置。 基础使用前提 调用 SDL_Init() 时必须传入 SDL_INIT_GAMEPAD 标志(SDL 会扫描系统游戏手柄并加载对应驱动); 若在 Steam 游戏中使用 SDL 游戏手柄功能,需先调用 SteamAPI_InitEx(),再初始化 SDL; 若需应用在后台时接收手柄事件,需在 SDL_Init() 前设置 SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS 提示; 应用必须支持手柄热插拔(Xbox、Steam Deck 等平台认证强制要求;macOS/Windows 使用 Windows.Gaming.Input 时,手柄可能在应用启动后才被识别)。 可选功能支持 游戏手柄支持震动、彩色 LED、触摸板、陀螺仪等可选功能(支持程度取决于手柄硬件和系统驱动): 运行时通过 SDL_GetGamepadProperties() 检查 LED/震动能力; 通过 SDL_GetNumGamepadTouchpads() 检查触摸板数量; 通过 SDL_GamepadHasSensor() 检查陀螺仪/加速度计是否可用; SDL 默认使用能力最强的驱动,也可通过 SDL_hints.h 中的摇杆相关提示调整系统驱动优先级。 函数 SDL_AddGamepadMapping:添加单个游戏手柄映射配置(传入魔术配置字符串) SDL_AddGamepadMappingsFromFile:从文件加载游戏手柄映射配置(批量导入) SDL_AddGamepadMappingsFromIO:从 IO 流加载游戏手柄映射配置(自定义数据源) SDL_CloseGamepad:关闭已打开的游戏手柄(释放手柄资源) SDL_GamepadConnected:检查指定游戏手柄是否处于已连接状态 SDL_GamepadEventsEnabled:检查游戏手柄事件是否启用(返回布尔值) SDL_GamepadHasAxis:检查游戏手柄是否支持指定的标准化轴(如左摇杆X轴) SDL_GamepadHasButton:检查游戏手柄是否支持指定的标准化按键(如 A 键) SDL_GamepadHasSensor:检查游戏手柄是否配备指定传感器(如陀螺仪、加速度计) SDL_GamepadSensorEnabled:检查游戏手柄指定传感器是否已启用 SDL_GetGamepadAppleSFSymbolsNameForAxis:获取苹果平台下游戏手柄轴对应的 SFSymbols 图标名称 SDL_GetGamepadAppleSFSymbolsNameForButton:获取苹果平台下游戏手柄按键对应的 SFSymbols 图标名称 SDL_GetGamepadAxis:获取游戏手柄指定标准化轴的当前值(范围:-32768 ~ 32767) SDL_GetGamepadAxisFromString:将字符串(如 "leftx")转换为 SDL_GamepadAxis 枚举值 SDL_GetGamepadBindings:获取游戏手柄的所有按键/轴绑定关系(标准化标识与硬件标识的映射) SDL_GetGamepadButton:获取游戏手柄指定标准化按键的当前状态(1=按下,0=释放) SDL_GetGamepadButtonFromString:将字符串(如 "a")转换为 SDL_GamepadButton 枚举值 SDL_GetGamepadButtonLabel:获取游戏手柄按键的标准化标签(如 A/B/X/Y 或 X/O/方块/三角) SDL_GetGamepadButtonLabelForType:根据手柄类型获取按键标签(适配 Xbox/PS 手柄差异) SDL_GetGamepadConnectionState:获取游戏手柄的连接状态(已连接/断开/正在连接) SDL_GetGamepadFirmwareVersion:获取游戏手柄的固件版本号 SDL_GetGamepadFromID:通过游戏手柄ID获取对应的 SDL_Gamepad 指针 SDL_GetGamepadFromPlayerIndex:通过玩家索引获取对应的 SDL_Gamepad 指针 SDL_GetGamepadGUIDForID:通过游戏手柄ID获取设备的 GUID 标识 SDL_GetGamepadID:获取已打开游戏手柄的实例ID SDL_GetGamepadJoystick:获取游戏手柄对应的底层摇杆设备指针 SDL_GetGamepadMapping:获取已打开游戏手柄的映射配置字符串 SDL_GetGamepadMappingForGUID:通过设备 GUID 获取对应的映射配置字符串 SDL_GetGamepadMappingForID:通过游戏手柄ID获取映射配置字符串 SDL_GetGamepadMappings:获取当前所有已加载的游戏手柄映射配置 SDL_GetGamepadName:获取已打开游戏手柄的设备名称 SDL_GetGamepadNameForID:通过游戏手柄ID获取设备名称 SDL_GetGamepadPath:获取已打开游戏手柄的系统路径 SDL_GetGamepadPathForID:通过游戏手柄ID获取设备系统路径 SDL_GetGamepadPlayerIndex:获取游戏手柄的玩家索引 SDL_GetGamepadPlayerIndexForID:通过游戏手柄ID获取玩家索引 SDL_GetGamepadPowerInfo:获取游戏手柄的电源信息(供电方式、电量百分比) SDL_GetGamepadProduct:获取游戏手柄的产品ID(PID) SDL_GetGamepadProductForID:通过游戏手柄ID获取产品ID SDL_GetGamepadProductVersion:获取游戏手柄的产品版本号 SDL_GetGamepadProductVersionForID:通过游戏手柄ID获取产品版本号 SDL_GetGamepadProperties:获取游戏手柄的属性集合(支持的功能:震动、LED、触摸板等) SDL_GetGamepads:枚举系统中所有已连接的游戏手柄(返回ID列表及数量) SDL_GetGamepadSensorData:获取游戏手柄传感器的最新数据(陀螺仪/加速度计数值) SDL_GetGamepadSensorDataRate:获取游戏手柄传感器的数据更新频率(Hz) SDL_GetGamepadSerial:获取游戏手柄的序列号(部分设备支持) SDL_GetGamepadSteamHandle:获取游戏手柄对应的 Steam 句柄(Steam 平台专用) SDL_GetGamepadStringForAxis:将 SDL_GamepadAxis 枚举值转换为可读字符串(如 "leftx") SDL_GetGamepadStringForButton:将 SDL_GamepadButton 枚举值转换为可读字符串(如 "a") SDL_GetGamepadStringForType:将 SDL_GamepadType 枚举值转换为可读字符串(如 "xbox360") SDL_GetGamepadTouchpadFinger:获取游戏手柄触摸板上指定手指的位置/压力信息 SDL_GetGamepadType:获取游戏手柄的类型(Xbox/PS/Switch 等) SDL_GetGamepadTypeForID:通过游戏手柄ID获取设备类型 SDL_GetGamepadTypeFromString:将字符串(如 "ps4")转换为 SDL_GamepadType 枚举值 SDL_GetGamepadVendor:获取游戏手柄的厂商ID(VID) SDL_GetGamepadVendorForID:通过游戏手柄ID获取厂商ID SDL_GetNumGamepadTouchpadFingers:获取游戏手柄指定触摸板支持的最大同时触摸手指数量 SDL_GetNumGamepadTouchpads:获取游戏手柄的触摸板数量 SDL_GetRealGamepadType:获取游戏手柄的实际硬件类型(区分虚拟/真实设备) SDL_GetRealGamepadTypeForID:通过游戏手柄ID获取实际硬件类型 SDL_HasGamepad:检查系统是否连接了至少一个游戏手柄(返回布尔值) SDL_IsGamepad:检查指定ID的设备是否为标准化游戏手柄(而非普通摇杆) SDL_OpenGamepad:打开指定ID的游戏手柄设备(返回 SDL_Gamepad 指针) SDL_ReloadGamepadMappings:重新加载所有游戏手柄映射配置(支持热更新) SDL_RumbleGamepad:启用游戏手柄的全局震动(设置左右电机强度和持续时间) SDL_RumbleGamepadTriggers:启用游戏手柄扳机键的震动(仅部分设备支持) SDL_SendGamepadEffect:发送自定义震动效果到游戏手柄(如特定的震动模式) SDL_SetGamepadEventsEnabled:启用/禁用游戏手柄事件(禁用后不再接收手柄相关事件) SDL_SetGamepadLED:设置游戏手柄LED灯的颜色(RGB值,部分设备支持) SDL_SetGamepadMapping:为指定GUID的设备设置映射配置字符串 SDL_SetGamepadPlayerIndex:设置游戏手柄的玩家索引 SDL_SetGamepadSensorEnabled:启用/禁用游戏手柄的指定传感器 SDL_UpdateGamepads:强制更新所有游戏手柄的状态(SDL 内部自动调用,一般无需手动调用) 数据类型 SDL_Gamepad:游戏手柄设备句柄类型(标识已打开的标准化游戏手柄) 结构体 SDL_GamepadBinding:游戏手柄绑定关系结构体(存储标准化按键/轴与硬件标识的映射) 枚举 SDL_GamepadAxis:游戏手柄标准化轴枚举(如 SDL_GAMEPAD_AXIS_LEFTX 左摇杆X轴、SDL_GAMEPAD_AXIS_RIGHTY 右摇杆Y轴) SDL_GamepadBindingType:游戏手柄绑定类型枚举(轴/按键/方向键等) SDL_GamepadButton:游戏手柄标准化按键枚举(如 SDL_GAMEPAD_BUTTON_A A键、SDL_GAMEPAD_BUTTON_START 开始键) SDL_GamepadButtonLabel:游戏手柄按键标签枚举(适配 Xbox/PS 手柄的按键标识差异) SDL_GamepadType:游戏手柄类型枚举(SDL_GAMEPAD_TYPE_XBOX360、SDL_GAMEPAD_TYPE_PS4 等) 宏 (无) FreeBASIC 示例代码 ' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库) #Include "SDL.bi" ' 辅助函数:打印游戏手柄基础信息 Sub PrintGamepadInfo(ByVal gamepad As SDL_Gamepad Ptr) If (gamepad = NULL) Then Return Dim As ZString Ptr name = SDL_GetGamepadName(gamepad) Dim As SDL_JoystickID padID = SDL_GetGamepadID(gamepad) Dim As SDL_GamepadType padType = SDL_GetGamepadType(gamepad) Dim As ZString Ptr typeStr = SDL_GetGamepadStringForType(padType) ' 硬件信息 Dim As Uint16 vendor = SDL_GetGamepadVendor(gamepad) Dim As Uint16 product = SDL_GetGamepadProduct(gamepad) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 游戏手柄信息 ====") SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "ID:%d | 名称:%s | 类型:%s", padID, name, typeStr) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "厂商ID:0x%X | 产品ID:0x%X", vendor, product) ' 检查可选功能 Dim As SDL_GamepadProperties props = SDL_GetGamepadProperties(gamepad) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "支持震动:%s | 支持LED:%s | 支持触摸板:%s", _ IIf(props.has_rumble, "是", "否"), _ IIf(props.has_led, "是", "否"), _ IIf(SDL_GetNumGamepadTouchpads(gamepad) > 0, "是", "否")) If (name <> NULL) Then SDL_free(name) If (typeStr <> NULL) Then SDL_free(typeStr) End Sub ' 辅助函数:处理游戏手柄输入(标准化按键/轴) Sub HandleGamepadInput(ByVal gamepad As SDL_Gamepad Ptr) If (gamepad = NULL) Then Return ' 处理左摇杆移动(标准化轴) Static As Integer lastLX = 0, lastLY = 0 Dim As Sint16 lx = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX) Dim As Sint16 ly = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY) ' 死区处理(过滤硬件漂移) Const DEADZONE = 8000 If (Abs(lx) < DEADZONE) Then lx = 0 If (Abs(ly) < DEADZONE) Then ly = 0 If (lx <> lastLX Or ly <> lastLY) Then If (lx = 0 And ly = 0) Then SDL_LogInfo(SDL_LOG_CATEGORY_GAME, "左摇杆:居中") Else ' 归一化轴值(-1.0 ~ 1.0) Dim As Double normLX = lx / 32767.0 Dim As Double normLY = ly / 32767.0 SDL_LogInfo(SDL_LOG_CATEGORY_GAME, "左摇杆:X=%.2f, Y=%.2f", normLX, normLY) End If lastLX = lx lastLY = ly End If ' 检查标准化按键(A键按下触发跳跃) Static As Boolean aPressed = False Dim As Integer aState = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_A) If (aState And Not aPressed) Then SDL_LogInfo(SDL_LOG_CATEGORY_GAME, "按下 A 键:跳跃!") ' 触发震动反馈 SDL_RumbleGamepad(gamepad, 0.3 * 0xFFFF, 0.7 * 0xFFFF, 200) End If aPressed = (aState <> 0) ' 检查右扳机键(RT) Static As Integer lastRT = 0 Dim As Sint16 rt = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_TRIGGERRIGHT) If (rt > DEADZONE And rt <> lastRT) Then Dim As Double normRT = rt / 32767.0 SDL_LogInfo(SDL_LOG_CATEGORY_GAME, "右扳机键:%.2f(开火强度)", normRT) lastRT = rt End If End Sub ' 主程序 Dim As SDL_Window Ptr window = NULL Dim As SDL_Gamepad Ptr gamepad = NULL Dim As SDL_JoystickID Ptr padIDs = NULL Dim As Integer padCount = 0 Dim As SDL_Event evt Dim As Boolean quit = False ' 1. 初始化 SDL(必须包含 GAMEPAD 标志) If (SDL_Init(SDL_INIT_VIDEO Or SDL_INIT_GAMEPAD) < 0) Then SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError()) End 1 End If ' 2. 创建窗口 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 ' 3. 枚举并打开游戏手柄 If (SDL_HasGamepad()) Then padCount = SDL_GetGamepads(0, NULL) If (padCount > 0) Then padIDs = Callocate(padCount * SizeOf(SDL_JoystickID)) padCount = SDL_GetGamepads(padCount, padIDs) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "检测到 %d 个游戏手柄", padCount) ' 打开第一个游戏手柄 If (SDL_IsGamepad(padIDs[0])) Then gamepad = SDL_OpenGamepad(padIDs[0]) If (gamepad <> NULL) Then PrintGamepadInfo(gamepad) SDL_SetGamepadEventsEnabled(SDL_TRUE) ' 启用陀螺仪(如果支持) If (SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYROSCOPE)) Then SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYROSCOPE, SDL_TRUE) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "已启用陀螺仪传感器") End If Else SDL_LogError(SDL_LOG_CATEGORY_INPUT, "打开游戏手柄失败:%s", SDL_GetError()) End If Else SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "设备 %d 不是标准化游戏手柄", padIDs[0]) End If End If Else SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "系统未检测到游戏手柄") End If SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL 游戏手柄示例 ===") SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "手柄操作:左摇杆移动 / A键跳跃 / RT键开火 | F1:打印手柄状态 | ESC:退出") ' 4. 主循环 While (Not quit) ' 每帧处理游戏手柄输入 If (gamepad <> NULL And SDL_GamepadConnected(gamepad)) Then HandleGamepadInput(gamepad) ' 读取陀螺仪数据(如果启用) If (SDL_GamepadSensorEnabled(gamepad, SDL_SENSOR_GYROSCOPE)) Then Dim As Float sensorData(3) If (SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYROSCOPE, @sensorData[0], 3) = 0) Then Static As Integer frame = 0 frame += 1 If (frame Mod 60 = 0) Then ' 每秒打印一次 SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "陀螺仪数据:X=%.2f, Y=%.2f, Z=%.2f", _ sensorData[0], sensorData[1], sensorData[2]) End If End If End If End If ' 处理事件队列 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 (gamepad <> NULL) Then PrintGamepadInfo(gamepad) End If ' F2:设置手柄LED颜色(红色) Case SDL_SCANCODE_F2 If (gamepad <> NULL) Then Dim As SDL_GamepadProperties props = SDL_GetGamepadProperties(gamepad) If (props.has_led) Then SDL_SetGamepadLED(gamepad, 255, 0, 0) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "设置手柄LED为红色") Else SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "该手柄不支持LED灯") End If End If End Select ' 游戏手柄插拔事件 Case SDL_EVENT_GAMEPAD_ADDED SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "游戏手柄已连接,ID:%d", evt.cdevice.which) ' 自动打开新连接的手柄 If (gamepad = NULL And SDL_IsGamepad(evt.cdevice.which)) Then gamepad = SDL_OpenGamepad(evt.cdevice.which) If (gamepad <> NULL) Then PrintGamepadInfo(gamepad) End If End If Case SDL_EVENT_GAMEPAD_REMOVED SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "游戏手柄已断开,ID:%d", evt.cdevice.which) If (gamepad <> NULL And SDL_GetGamepadID(gamepad) = evt.cdevice.which) Then SDL_CloseGamepad(gamepad) gamepad = NULL End If ' 游戏手柄按键事件(标准化) Case SDL_EVENT_GAMEPAD_BUTTON_DOWN Dim As ZString Ptr btnStr = SDL_GetGamepadStringForButton(evt.cbutton.button) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "手柄按键 %s 按下(设备ID:%d)", btnStr, evt.cbutton.which) SDL_free(btnStr) Case SDL_EVENT_GAMEPAD_BUTTON_UP Dim As ZString Ptr btnStr = SDL_GetGamepadStringForButton(evt.cbutton.button) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "手柄按键 %s 释放(设备ID:%d)", btnStr, evt.cbutton.which) SDL_free(btnStr) ' 游戏手柄轴事件(标准化) Case SDL_EVENT_GAMEPAD_AXIS_MOTION Dim As ZString Ptr axisStr = SDL_GetGamepadStringForAxis(evt.caxis.axis) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "手柄轴 %s 移动:%d(设备ID:%d)", _ axisStr, evt.caxis.value, evt.caxis.which) SDL_free(axisStr) End Select Wend SDL_Delay(10) Wend ' 5. 清理资源 If (gamepad <> NULL) Then SDL_CloseGamepad(gamepad) End If If (padIDs <> NULL) Then Deallocate(padIDs) End If SDL_DestroyWindow(window) SDL_Quit() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出") End Sub 核心知识点补充 标准化轴/按键枚举(游戏开发高频使用): 轴枚举 说明 按键枚举 说明 SDL_GAMEPAD_AXIS_LEFTX 左摇杆X轴 SDL_GAMEPAD_BUTTON_A A 键(确认) SDL_GAMEPAD_AXIS_LEFTY 左摇杆Y轴 SDL_GAMEPAD_BUTTON_B B 键(取消) SDL_GAMEPAD_AXIS_RIGHTX 右摇杆X轴 SDL_GAMEPAD_BUTTON_X X 键 SDL_GAMEPAD_AXIS_RIGHTY 右摇杆Y轴 SDL_GAMEPAD_BUTTON_Y Y 键 SDL_GAMEPAD_AXIS_TRIGGERLEFT 左扳机键 SDL_GAMEPAD_BUTTON_START 开始键 SDL_GAMEPAD_AXIS_TRIGGERRIGHT 右扳机键 SDL_GAMEPAD_BUTTON_BACK 返回键 死区处理必要性: 物理摇杆存在硬件漂移,即使未触碰也会有微小数值; 通常设置 8000~10000 的死区阈值,低于阈值视为 0; 死区值需根据手柄硬件调整,避免操作不灵敏。 跨平台适配: SDL 自动适配 Xbox/PS/Switch 手柄的按键标签差异; 通过 SDL_GetGamepadButtonLabel 获取平台化的按键标识; Steam 平台需优先初始化 Steam API,确保手柄功能正常。 总结 核心优势: 标准化输入标识,无需关心硬件底层的按键/轴编号; 内置主流手柄配置,开箱即用,大幅降低适配成本; 支持震动、LED、传感器等高级功能的统一调用接口; 使用建议: 绝大多数游戏场景优先使用 Gamepad API,而非底层 Joystick API; 必须支持热插拔,适配手柄动态连接/断开; 轴输入需做死区处理,提升操作手感; 关键流程: 初始化(SDL_INIT_GAMEPAD)→ 枚举设备 → 检查是否为标准化手柄 → 打开手柄 → 处理标准化事件/查询状态 → 关闭手柄。
2026年-3月-6日
2 阅读
0 评论
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_摇杆(CategoryJoystick)
摇杆子系统(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 等功能仅部分设备支持,需做兼容性检查。
2026年-3月-6日
5 阅读
0 评论
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_鼠标(CategoryMouse)
鼠标子系统(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 枚举鼠标设备,监听插拔事件实现多鼠标差异化处理。
2026年-3月-6日
5 阅读
0 评论
VisualFreeBasic编程文档
2026-3-6
SDL3_API分类参考_扫描码(CategoryScancode)
扫描码子系统(CategoryScancode) 该模块定义了键盘扫描码(Scancode)的核心枚举类型,扫描码用于标识键盘上物理按键的位置,而非按键上的符号。 关于扫描码的具体含义和最佳使用方式,请参考官方键盘使用最佳实践文档: https://wiki.libsdl.org/SDL3/BestKeyboardPractices 函数 (无) 数据类型 (无) 结构体 (无) 枚举 SDL_Scancode:键盘扫描码枚举(每个值对应键盘上一个固定的物理位置,参考美式 QWERTY 键盘布局,与按键符号/键盘布局无关) 宏 (无) FreeBASIC 示例代码 ' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库) #Include "SDL.bi" ' 辅助函数:打印扫描码详细信息 Sub PrintScancodeInfo(ByVal scancode As SDL_Scancode) ' 获取扫描码名称 Dim As ZString Ptr scName = SDL_GetScancodeName(scancode) ' 扫描码转按键码(体现布局差异) Dim As SDL_Keycode keycode = SDL_GetKeyFromScancode(scancode) Dim As ZString Ptr keyName = SDL_GetKeyName(keycode) SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _ "扫描码:%d (0x%X) | 扫描码名称:%s | 对应按键码:0x%X | 按键名称:%s", _ scancode, scancode, scName, keycode, keyName) ' 释放字符串资源 SDL_free(scName) SDL_free(keyName) End Sub ' 模拟 WASD 移动(核心场景:基于物理位置的输入) Sub HandleWASDMovement() Static As Integer lastDirX = 0, lastDirY = 0 Dim As UByte Ptr keyStates = SDL_GetKeyboardState(NULL) ' 基于扫描码的物理位置检测(与布局无关) Dim As Integer dirX = 0, dirY = 0 If (keyStates[SDL_SCANCODE_A]) Then dirX -= 1 ' A 键位置:左 If (keyStates[SDL_SCANCODE_D]) Then dirX += 1 ' D 键位置:右 If (keyStates[SDL_SCANCODE_W]) Then dirY -= 1 ' W 键位置:上 If (keyStates[SDL_SCANCODE_S]) Then dirY += 1 ' S 键位置:下 ' 仅在方向变化时打印日志 If (dirX <> lastDirX Or dirY <> lastDirY) Then If (dirX = 0 And dirY = 0) Then SDL_LogInfo(SDL_LOG_CATEGORY_GAME, "停止移动") Else Dim As String dirStr = "" If (dirY < 0) Then dirStr &= "上 " If (dirY > 0) Then dirStr &= "下 " If (dirX < 0) Then dirStr &= "左 " If (dirX > 0) Then dirStr &= "右 " SDL_LogInfo(SDL_LOG_CATEGORY_GAME, "移动方向:%s", dirStr) End If lastDirX = dirX lastDirY = dirY End If 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 Scancode 示例", _ 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_Scancode 核心示例 ===") SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "WASD:移动(基于物理位置)| 按任意键查看扫描码信息 | ESC:退出") ' 3. 主循环 While (Not quit) ' 每帧处理 WASD 移动(状态查询) HandleWASDMovement() ' 处理事件队列 While (SDL_PollEvent(@evt)) Select Case evt.type Case SDL_QUITEVENT quit = True Case SDL_EVENT_KEY_DOWN ' ESC 退出 If (evt.key.scancode = SDL_SCANCODE_ESCAPE) Then SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下 ESC 键(扫描码),退出程序") quit = True Else ' 打印当前按键的扫描码信息 SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 按键物理位置信息 ====") PrintScancodeInfo(evt.key.scancode) End If ' 演示:扫描码转名称(自定义绑定场景) If (evt.key.scancode = SDL_SCANCODE_SPACE) Then Dim As ZString Ptr spaceName = SDL_GetScancodeName(SDL_SCANCODE_SPACE) SDL_LogInfo(SDL_LOG_CATEGORY_GAME, "按下 %s 键(物理位置),触发跳跃", spaceName) SDL_free(spaceName) End If End Select Wend SDL_Delay(10) ' 降低 CPU 占用 Wend ' 4. 清理资源 SDL_DestroyWindow(window) SDL_Quit() SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出") End 0 核心扫描码枚举说明 SDL_Scancode 枚举值完全对应美式 QWERTY 键盘的物理位置,以下是游戏开发中最常用的核心值: 扫描码常量 物理位置(美式键盘) 典型用途 SDL_SCANCODE_W W 键 上/前进 SDL_SCANCODE_A A 键 左 SDL_SCANCODE_S S 键 下/后退 SDL_SCANCODE_D D 键 右 SDL_SCANCODE_SPACE 空格键 跳跃/确认 SDL_SCANCODE_ESCAPE ESC 键 取消/退出 SDL_SCANCODE_SHIFT Shift 键(左右均可) 加速/大写 SDL_SCANCODE_CTRL Ctrl 键(左右均可) 特殊操作 SDL_SCANCODE_R R 键 换弹/刷新 SDL_SCANCODE_F1-F12 F1-F12 功能键 功能快捷键 SDL_SCANCODE_UP/DOWN/LEFT/RIGHT 方向键 菜单导航/移动 扫描码 vs 按键码 核心区别 特性 SDL_Scancode(扫描码) SDL_Keycode(按键码) 绑定对象 物理按键位置(与布局无关) 按键符号(与布局相关) 核心场景 游戏移动(WASD)、快捷键(物理位置) 文本输入、通用按键(ESC/Enter) 示例 法语键盘按 Z 键 → SDL_SCANCODE_W 法语键盘按 Z 键 → SDLK_z 稳定性 跨布局稳定(位置不变) 跨布局变化(符号不变) 总结 SDL_Scancode 核心价值:绑定键盘物理位置,是游戏中「设备无关输入」的基础(如 WASD 移动在任何布局的键盘上都对应相同的手指位置); 使用场景:所有基于「物理按键位置」的输入(移动、射击、快捷键)都应优先使用扫描码; 关键配合函数: SDL_GetKeyboardState:通过扫描码查询实时按键状态; SDL_GetScancodeName:获取扫描码的可读名称(用于配置界面); SDL_GetKeyFromScancode:扫描码转按键码(适配布局的符号显示)。
2026年-3月-6日
4 阅读
0 评论
VisualFreeBasic编程文档
2
3
4
5
6