当前位置: 首页 > news >正文

分步详解:凤凰6000模拟器接入Unity Input System‌(

分步详解:凤凰6000模拟器接入Unity Input System

(每行代码均包含详细注释)

前提条件:

  1. 安装 Unity Input System: 确保你的 Unity 项目已经通过 Package Manager 安装了 Input System 包。如果没有,请前往 Window -> Package Manager,选择 Unity Registry,搜索 Input System 并安装。

  2. 启用 Input System: 在 Edit -> Project Settings -> Player -> Other Settings 中,找到 Active Input Handling 选项,将其设置为 Input System Package (New) 或者 Both。Unity 会提示重启编辑器。

步骤一:找到凤凰SM600手柄的 VID 和 PID

这是识别你设备的“身份证号”。

  1. 连接手柄: 将凤凰SM600手柄通过 USB 连接到你的 Windows 电脑。

  2. 打开设备管理器:

    • 在 Windows 搜索栏搜索“设备管理器”并打开。

    • 或者右键点击“此电脑” -> “管理” -> “设备管理器”。

  3. 找到你的手柄: 在设备列表中查找,它可能在“人体学输入设备 (HID)”、"通用串行总线控制器" 下,或者显示为设备名称(如 "Phoenix SM600" 或类似名称)。仔细查找,可能显示为 "USB 输入设备" 或 "HID-compliant game controller"。

    • 提示: 如果不确定是哪个设备,可以尝试拔掉手柄再插上,观察设备列表的变化。

  4. 查看属性: 找到设备后,右键点击它,选择“属性”。

  5. 查找 VID 和 PID:

    • 切换到“详细信息”选项卡。

    • 在“属性”下拉菜单中,选择“硬件 ID”。

    • 你会看到类似 HID\VID_xxxx&PID_xxxx 或 USB\VID_xxxx&PID_xxxx 的值。这里的 xxxx 就是你需要记下的 Vendor ID (VID) 和 Product ID (PID)。它们通常是 4 位的十六进制数(例如 054C 或 09CC)。 请记下你找到的实际 VID 和 PID 值。


步骤1:获取设备VID/PID.

操作流程

  1. 连接凤凰6000模拟器到电脑
  2. 打开设备管理器 → 右键设备 → ‌属性 → 详细信息 → 硬件ID
  3. 记录VID_XXXXPID_XXXX(例如:VID_1234&PID_5678
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Layouts; // <--- 添加这个
using UnityEngine.InputSystem.HID;
using UnityEngine.InputSystem.Utilities; // <--- 添加这个
using UnityEngine.InputSystem.LowLevel;
using System.Runtime.InteropServices;
using UnityEditor;
#if UNITY_EDITOR
using UnityEditor;
#endif// 1. 定义设备布局结构体 (这是最关键也最难的部分,下面会详细说明)
//    你需要根据实际抓包分析来确定这个结构
//    下面的结构体是一个 *假设* 的例子,你需要替换它!
[StructLayout(LayoutKind.Explicit, Size = 16)] // 假设报告大小为 16 字节, 你需要改成实际大小
public struct PhoenixSM600HIDInputReport : IInputStateTypeInfo
{// HID 报告通常以 Report ID 开始 (如果设备使用的话)[FieldOffset(0)] public byte reportId; // 如果没有 report ID,可以注释掉或将 Size 和后续 Offset 调整// --- 假设的摇杆和按钮布局 ---// 你必须通过工具分析实际的数据包来确定这些!// 假设左摇杆 X/Y (各占 1 字节)[InputControl(name = "leftStick", layout = "Stick", format = "VC2B")] // 定义一个 Stick 控件[InputControl(name = "leftStick/x", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=255,normalizeZero=128")] // 对应 leftStickX[InputControl(name = "leftStick/y", offset = 2, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=255,normalizeZero=128,invert")] // 对应 leftStickY (Y轴通常需要反转)[FieldOffset(1)] public byte leftStickX;[FieldOffset(2)] public byte leftStickY;// 假设右摇杆 X/Y (各占 1 字节)[InputControl(name = "rightStick", layout = "Stick", format = "VC2B")][InputControl(name = "rightStick/x", offset = 3, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=255,normalizeZero=128")][InputControl(name = "rightStick/y", offset = 4, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=255,normalizeZero=128,invert")][FieldOffset(3)] public byte rightStickX;[FieldOffset(4)] public byte rightStickY;// 假设有 8 个按钮,用一个字节的位来表示 (每个 bit 是一个按钮)[InputControl(name = "buttonSouth", layout = "Button", bit = 0, offset = 5)] // A/Cross[InputControl(name = "buttonEast", layout = "Button", bit = 1, offset = 5)]  // B/Circle[InputControl(name = "buttonWest", layout = "Button", bit = 2, offset = 5)]  // X/Square[InputControl(name = "buttonNorth", layout = "Button", bit = 3, offset = 5)] // Y/Triangle[InputControl(name = "leftShoulder", layout = "Button", bit = 4, offset = 5)] // L1[InputControl(name = "rightShoulder", layout = "Button", bit = 5, offset = 5)]// R1[InputControl(name = "leftTriggerButton", layout = "Button", bit = 6, offset = 5)] // L2 Press (不是模拟量)[InputControl(name = "rightTriggerButton", layout = "Button", bit = 7, offset = 5)]// R2 Press (不是模拟量)[FieldOffset(5)] public byte buttons1;// 假设还有其他按钮或轴... 你需要继续添加// [FieldOffset(6)] public byte someOtherData;// ...// --- 结束假设布局 ---// 实现 IInputStateTypeInfo 接口// FourCC for "HID "public FourCC format => new FourCC('H', 'I', 'D');
}// 2. 注册设备布局
//    这个类告诉 Input System 如何识别你的设备 (通过 VID/PID)
//    并指定使用上面定义的 PhoenixSM600HIDInputReport 结构来解析数据
#if UNITY_EDITOR
[InitializeOnLoad] // 确保在编辑器启动时注册
#endif
// 使用 [InputControlLayout] 将设备类与上面的结构体关联起来
// stateType 指向你的 HID 输入报告结构体
// displayName 是在 Input Debugger 里显示的名称
[InputControlLayout(stateType = typeof(PhoenixSM600HIDInputReport), displayName = "Phoenix SM600 Drone Controller")]
public class PhoenixSM600ControllerSupport : Gamepad // 可以继承自 Gamepad, Joystick, 或者更通用的 InputDevice
{static PhoenixSM600ControllerSupport(){// 使用 VID 和 PID 注册设备// !!! 将下面的 0xYYYY 和 0xZZZZ 替换为你找到的实际 VID 和 PID !!!InputSystem.RegisterLayout<PhoenixSM600ControllerSupport>(matches: new InputDeviceMatcher().WithInterface("HID") // 必须是 HID 设备.WithCapability("vendorId", 0x1781) // !!! 替换 VENDOR_ID !!! e.g. 0x1234.WithCapability("productId", 0x0898) // !!! 替换 PRODUCT_ID !!! e.g. 0x5678);Debug.Log("Phoenix SM600 Controller layout registered.");}// 在运行时初始化,确保 Player 构建时也能注册[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]static void InitializeInPlayer(){// 这个空方法调用会触发上面的静态构造函数}
}

步骤三:确定并定义正确的输入报告结构体 (PhoenixSM600HIDInputReport)

这是最关键也是最困难的一步,因为你需要知道手柄发送数据的确切格式。 上面代码中的 PhoenixSM600HIDInputReport 结构体只是一个 完全假设 的例子,你必须用实际的数据结构替换它!

如何获取实际的数据结构?

你需要使用 HID 分析工具 来查看手柄实际发送的原始数据。

  1. 获取工具:

    • 推荐: HID Demo (SimpleHIDWrite 的一部分) 或类似的免费 HID 调试工具。

    • 其他选项: USBPcap + Wireshark (更复杂,功能更强大),或者商业工具如 USBlyzer。

  2. 使用工具:

    • 运行 HID 分析工具。

    • 连接你的凤凰SM600手柄。

    • 在工具中找到你的手柄设备。

    • 工具通常会显示手柄发送的原始数据包(一串十六进制字节)。

  3. 分析数据:

    • 观察静止状态: 记下所有字节的默认值。

    • 移动单个控件: 缓慢移动左摇杆的 X 轴,观察哪些字节发生了变化以及如何变化(例如,从 0x80 变到 0x00 或 0xFF)。记下变化的字节偏移量(是第几个字节,从 0 开始计数)和数据范围

    • 重复操作: 对每个摇杆轴 (X/Y)、每个按钮、每个开关、每个旋钮重复此过程。

    • 确定报告大小: 确定每次发送的数据包总共有多少字节。这就是结构体 StructLayout 中的 Size 参数。

    • 确定数据格式:

      • 摇杆轴通常是 1 个字节 (BYTE, 0-255) 或 2 个字节 (SHORT, -32768 到 32767)。

      • 按钮可能是单个位 (bit),或者用一个字节的值表示 (按下是某个值,松开是另一个值)。

  4. 修改 C# 结构体 (PhoenixSM600HIDInputReport):

    • 调整 Size: 将 [StructLayout(LayoutKind.Explicit, Size = XX)] 中的 XX 改为你分析出的实际报告字节数。

    • 添加 FieldOffset: 为每个你识别出的数据块(如 leftStickX, buttons1)添加 [FieldOffset(N)] public byte/short/int fieldName;。N 是该数据在报告中的起始字节偏移量(从 0 开始)。

    • 添加 InputControl 属性: 在对应的 FieldOffset 字段 上方 添加 [InputControl(...)] 属性来将其映射到 Input System 的控件。

      • name: 控件的标准名称 (如 "leftStick/x", "buttonSouth")。参考 Input System 文档的标准名称。

      • layout: 控件类型 (如 "Stick", "Button", "Axis")。

      • format: 数据类型 (如 "BYTE", "SHRT", "BIT")。必须与你的字段类型和实际数据匹配。

      • offset: 对于非位域(bit field)控件,这个 offset 指的是 FieldOffset 定义的字段内的字节偏移量。 如果一个字段(如 leftStickX)直接对应一个控件,通常 offset 可以省略或设为 0。但是,如果你的 Stick 布局引用了两个分开定义的字节字段(如 leftStickX 和 leftStickY),那么 Stick 本身的 InputControl 不需要 offset,而 /x 和 /y 子控件的 InputControl 才需要 offset 来指定它们对应哪个 FieldOffset。 更正:对于 InputControl 属性,offset 指的是相对于整个状态结构体起始位置的字节偏移量。 因此,你需要确保 [InputControl(... offset = N ...)] 中的 N 与其关联的 [FieldOffset(N)] 中的 N 一致(除非是像 Stick 这样的复合控件,它会引用子控件的 offset)。

      • bit: 仅用于 Button layout,指定该按钮对应 FieldOffset 字段中的哪一位(0-7)。

      • parameters: 非常重要!用于数据处理。

        • normalize,normalizeMin=0,normalizeMax=255,normalizeZero=128: 将原始范围 (0-255) 映射到标准范围 (-1 到 1 或 0 到 1)。你需要根据分析出的数据范围调整 normalizeMin, normalizeMax, normalizeZero。normalizeZero 是静止状态的值。

        • invert: 如果轴的方向反了,添加 invert 或 invert=true。

        • 其他参数如 clamp 等,根据需要添加。

示例(假设分析结果):

假设你分析发现:

  • 报告大小是 8 字节。

  • 第一个字节 (Offset 0) 是左摇杆 X (0-255, 中间值 128)。

  • 第二个字节 (Offset 1) 是左摇杆 Y (0-255, 中间值 128)。

  • 第三个字节 (Offset 2) 是右摇杆 X (0-255, 中间值 128)。

  • 第四个字节 (Offset 3) 是右摇杆 Y (0-255, 中间值 128)。

  • 第五个字节 (Offset 4) 的第 0 位是按钮 A,第 1 位是按钮 B。

那么你的结构体应该类似这样修改:

[StructLayout(LayoutKind.Explicit, Size = 8)] // 改为实际大小 8
public struct PhoenixSM600HIDInputReport : IInputStateTypeInfo
{public FourCC format => new FourCC('H', 'I', 'D');// 左摇杆 X @ Offset 0[InputControl(name = "leftStick/x", layout = "Axis", format = "BYTE", offset = 0,parameters = "normalize,normalizeMin=0,normalizeMax=255,normalizeZero=128")][FieldOffset(0)] public byte leftStickX;// 左摇杆 Y @ Offset 1 (假设需要反转)[InputControl(name = "leftStick/y", layout = "Axis", format = "BYTE", offset = 1,parameters = "normalize,normalizeMin=0,normalizeMax=255,normalizeZero=128,invert")][FieldOffset(1)] public byte leftStickY;// 右摇杆 X @ Offset 2[InputControl(name = "rightStick/x", layout = "Axis", format = "BYTE", offset = 2,parameters = "normalize,normalizeMin=0,normalizeMax=255,normalizeZero=128")][FieldOffset(2)] public byte rightStickX;// 右摇杆 Y @ Offset 3 (假设需要反转)[InputControl(name = "rightStick/y", layout = "Axis", format = "BYTE", offset = 3,parameters = "normalize,normalizeMin=0,normalizeMax=255,normalizeZero=128,invert")][FieldOffset(3)] public byte rightStickY;// 按钮 @ Offset 4 (位域)[InputControl(name = "buttonSouth", layout = "Button", bit = 0, offset = 4)] // Bit 0[InputControl(name = "buttonEast", layout = "Button", bit = 1, offset = 4)]  // Bit 1// ... 可以继续定义 bit 2 到 7 如果有的话 ...[FieldOffset(4)] public byte buttonsByte1;// 还可以定义 Stick 复合控件 (可选, 但推荐)[InputControl(name = "leftStick", layout = "Stick", format = "VC2B", offset = 0)] // offset=0 指向 X[InputControl(name = "rightStick", layout = "Stick", format = "VC2B", offset = 2)] // offset=2 指向 X// 注意: Stick 的 x/y 子控件定义已在上面完成,这里只是定义复合控件本身
}

步骤四:测试和调试

  1. 保存脚本: 保存你的 C# 脚本。

  2. 返回 Unity: Unity 会自动编译脚本。检查 Console 窗口是否有编译错误。如果注册成功,你应该能在 Console 看到 "Phoenix SM600 Controller layout registered." 的消息。

  3. 打开 Input Debugger: 前往 Window -> Analysis -> Input Debugger。

  4. 连接手柄(如果尚未连接):

  5. 查找设备: 在 Input Debugger 窗口的左侧设备列表中,你应该能看到你的手柄,名称可能就是你在 [InputControlLayout] 中设置的 displayName("Phoenix SM600 Drone Controller")或者它被识别为基础类型(如 Gamepad)。展开设备。

  6. 测试控件:

    • 点击设备名称,右侧会显示状态 (State) 和控件 (Controls)。

    • 移动摇杆和按按钮: 观察右侧 "State" 视图中对应的字节值是否按预期变化。

    • 检查控件值: 在 "Controls" 列表中找到你定义的控件(如 leftStick, leftStick/x, buttonSouth),观察它们的值是否在你操作手柄时正确变化(例如,摇杆轴在 -1 到 1 或 0 到 1 之间变化,按钮在按下时值为 1,松开为 0)。

  7. 调试:

    • 设备未显示或显示为不支持 (Unsupported): 检查 VID/PID 是否正确填入注册脚本中。检查脚本是否有编译错误。

    • 设备已显示,但操作无反应或值全为 0:

      • 最可能的原因是 PhoenixSM600HIDInputReport 结构体定义错误。返回步骤三,仔细检查 HID 分析结果,确保 Size, FieldOffset, format 都正确。

      • 检查 [InputControl] 的 offset 是否指向了正确的字节。

    • 轴范围不正确 (不是 -1 到 1 或 0 到 1): 调整 [InputControl] 中的 parameters,特别是 normalizeMin, normalizeMax, normalizeZero。

    • 轴方向反了: 在 parameters 中添加 ,invert。

    • 按钮映射错误: 检查 [InputControl] 的 bit 参数(如果是位域按钮)或 offset。

步骤五:在游戏中使用手柄

一旦你在 Input Debugger 中确认所有控件都按预期工作,你就可以像使用任何其他受支持的手柄一样使用它了:

  1. 创建 Input Actions Asset: 在 Project 窗口右键 -> Create -> Input Actions。

  2. 编辑 Actions Map: 双击打开该资产,创建 Action Map(如 "Gameplay"),然后添加 Actions(如 "Move", "Look", "Fire")。

  3. 绑定控件: 为每个 Action 添加绑定 (Binding)。点击 "+" 号 -> Add Binding。在右侧的 Path 下拉菜单中,应该能找到你的 "Phoenix SM600 Drone Controller" 或其下的具体控件(如 Left Stick [Phoenix SM600 Drone Controller], Button South [Phoenix SM600 Drone Controller])。将它们绑定到对应的 Action。

  4. 使用 Player Input Component: 在你的玩家或其他需要输入的 GameObject 上添加 Player Input 组件。将你的 Input Actions Asset 拖入 Actions 字段。设置 Behavior (如 Send Messages, Invoke Unity Events) 来响应输入。

  5. 或者通过 C# 脚本访问: 你也可以在脚本中直接引用 Input Actions Asset 并读取输入值。

总结关键点:

  • 找到正确的 VID/PID 是第一步。

  • 最困难的部分是准确定义 IInputStateTypeInfo 结构体 (PhoenixSM600HIDInputReport),这需要使用外部 HID 分析工具并仔细分析数据。 代码示例中的结构体几乎肯定需要根据你的分析结果进行大幅修改。

  • [InputControl] 属性是连接原始数据和 Input System 控件的桥梁,offset, format, bit, parameters 都至关重要。

  • Input Debugger 是你最好的调试工具,用它来验证你的结构体和控件映射是否正确。

祝你成功!这个过程可能需要一些耐心,特别是在分析 HID 数据那一步。

关键参数说明表

代码/参数作用与目的
StructLayout(LayoutKind.Explicit)精确控制结构体内存布局,确保与HID报告二进制对齐
[FieldOffset(N)]指定字段在结构体中的字节偏移量(必须与实际设备数据一致)
FourCC('H','I','D')标识输入数据格式为HID(不可修改)
InputControlLayout将自定义设备类与HID报告结构体绑定
InputDeviceMatcher通过VID/PID匹配设备,避免与其他HID设备冲突
RuntimeInitializeOnLoadMethodUnity运行时自动调用设备注册方法(无需手动触发)

常见问题解决方案

  1. 设备未识别

    • 检查VID/PID是否与设备管理器中的值完全一致(注意十六进制大小写)
    • 确保结构体Size与设备报告长度一致(可通过Wireshark抓包验证)
  2. 输入值始终为0

    • 使用HIDAPI调试工具确认设备发送数据正常
    • 检查[FieldOffset]偏移量是否与实际数据位置对齐
  3. Unity崩溃或报错

    • 确保项目设置中启用新版Input System:
      Edit > Project Settings > Player > Active Input Handling → Both
    • 更新Unity至2020.3+,并安装最新版Input System Package

最终验证

  1. 在场景中挂载Phoenix6000Tester脚本
  2. 运行Unity,操作凤凰6000模拟器的摇杆
  3. 控制台应实时输出X/Y轴数据

相关文章:

  • antd中的表格穿梭框(Transfer)如何使用
  • npm打包内存不足- JavaScript heap out of memory
  • 【LeetCode】螺旋矩阵
  • LeetCode热题100--53.最大子数组和--中等
  • 前端在平常的开发中高度还原ui图的思考规范
  • 婴幼儿托育实训室生活照料流程标准化设计
  • 第三部分:赋予网页灵魂 —— JavaScript(下)
  • 味精(谷氨酸钠)是否健康(马井堂)
  • ESP32通过MQTT协议上传数据至阿里云物联网平台
  • NS-SWIFT微调Qwen3
  • CF4C Registration system(哈希实现)
  • AnimateCC基础教学:漫天繁星-由DeepSeek辅助完成
  • day31 第八章 贪心算法 part05
  • 生活需要一些思考
  • ppt箭头素材图片大全
  • 如何提升自我价值?
  • std::string的底层实现 (详解)
  • [4-06-09].第10节:自动配置- 分析@SpringBootApplication启动类
  • 防爆风扇储能轴流风机风量风压如何保障通风安全?
  • java每日精进 4.29【框架之自动记录日志并插入如数据库流程分析】
  • 上海市十六届人大常委会第二十一次会议表决通过有关人事任免事项
  • 日本希望再次租借大熊猫,外交部:双方就相关合作保持密切沟通
  • 昆明破获一起算命破灾诈骗案,民警:大师算不到自己的未来
  • 解放日报头版聚焦“人民城市”:共建共享展新卷
  • “上报集团文化助力区域高质量发展赋能平台”揭牌
  • 国家核安全局局长:我国核电进入大规模建设高峰期,在建规模超其他国家总和