分步详解:凤凰6000模拟器接入Unity Input System(
分步详解:凤凰6000模拟器接入Unity Input System
(每行代码均包含详细注释)
前提条件:
-
安装 Unity Input System: 确保你的 Unity 项目已经通过 Package Manager 安装了 Input System 包。如果没有,请前往 Window -> Package Manager,选择 Unity Registry,搜索 Input System 并安装。
-
启用 Input System: 在 Edit -> Project Settings -> Player -> Other Settings 中,找到 Active Input Handling 选项,将其设置为 Input System Package (New) 或者 Both。Unity 会提示重启编辑器。
步骤一:找到凤凰SM600手柄的 VID 和 PID
这是识别你设备的“身份证号”。
-
连接手柄: 将凤凰SM600手柄通过 USB 连接到你的 Windows 电脑。
-
打开设备管理器:
-
在 Windows 搜索栏搜索“设备管理器”并打开。
-
或者右键点击“此电脑” -> “管理” -> “设备管理器”。
-
-
找到你的手柄: 在设备列表中查找,它可能在“人体学输入设备 (HID)”、"通用串行总线控制器" 下,或者显示为设备名称(如 "Phoenix SM600" 或类似名称)。仔细查找,可能显示为 "USB 输入设备" 或 "HID-compliant game controller"。
-
提示: 如果不确定是哪个设备,可以尝试拔掉手柄再插上,观察设备列表的变化。
-
-
查看属性: 找到设备后,右键点击它,选择“属性”。
-
查找 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.
操作流程
- 连接凤凰6000模拟器到电脑
- 打开
设备管理器
→ 右键设备 → 属性 → 详细信息 → 硬件ID - 记录
VID_XXXX
和PID_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 分析工具 来查看手柄实际发送的原始数据。
-
获取工具:
-
推荐: HID Demo (SimpleHIDWrite 的一部分) 或类似的免费 HID 调试工具。
-
其他选项: USBPcap + Wireshark (更复杂,功能更强大),或者商业工具如 USBlyzer。
-
-
使用工具:
-
运行 HID 分析工具。
-
连接你的凤凰SM600手柄。
-
在工具中找到你的手柄设备。
-
工具通常会显示手柄发送的原始数据包(一串十六进制字节)。
-
-
分析数据:
-
观察静止状态: 记下所有字节的默认值。
-
移动单个控件: 缓慢移动左摇杆的 X 轴,观察哪些字节发生了变化以及如何变化(例如,从 0x80 变到 0x00 或 0xFF)。记下变化的字节偏移量(是第几个字节,从 0 开始计数)和数据范围。
-
重复操作: 对每个摇杆轴 (X/Y)、每个按钮、每个开关、每个旋钮重复此过程。
-
确定报告大小: 确定每次发送的数据包总共有多少字节。这就是结构体 StructLayout 中的 Size 参数。
-
确定数据格式:
-
摇杆轴通常是 1 个字节 (BYTE, 0-255) 或 2 个字节 (SHORT, -32768 到 32767)。
-
按钮可能是单个位 (bit),或者用一个字节的值表示 (按下是某个值,松开是另一个值)。
-
-
-
修改 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 子控件定义已在上面完成,这里只是定义复合控件本身
}
步骤四:测试和调试
-
保存脚本: 保存你的 C# 脚本。
-
返回 Unity: Unity 会自动编译脚本。检查 Console 窗口是否有编译错误。如果注册成功,你应该能在 Console 看到 "Phoenix SM600 Controller layout registered." 的消息。
-
打开 Input Debugger: 前往 Window -> Analysis -> Input Debugger。
-
连接手柄(如果尚未连接):
-
查找设备: 在 Input Debugger 窗口的左侧设备列表中,你应该能看到你的手柄,名称可能就是你在 [InputControlLayout] 中设置的 displayName("Phoenix SM600 Drone Controller")或者它被识别为基础类型(如 Gamepad)。展开设备。
-
测试控件:
-
点击设备名称,右侧会显示状态 (State) 和控件 (Controls)。
-
移动摇杆和按按钮: 观察右侧 "State" 视图中对应的字节值是否按预期变化。
-
检查控件值: 在 "Controls" 列表中找到你定义的控件(如 leftStick, leftStick/x, buttonSouth),观察它们的值是否在你操作手柄时正确变化(例如,摇杆轴在 -1 到 1 或 0 到 1 之间变化,按钮在按下时值为 1,松开为 0)。
-
-
调试:
-
设备未显示或显示为不支持 (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 中确认所有控件都按预期工作,你就可以像使用任何其他受支持的手柄一样使用它了:
-
创建 Input Actions Asset: 在 Project 窗口右键 -> Create -> Input Actions。
-
编辑 Actions Map: 双击打开该资产,创建 Action Map(如 "Gameplay"),然后添加 Actions(如 "Move", "Look", "Fire")。
-
绑定控件: 为每个 Action 添加绑定 (Binding)。点击 "+" 号 -> Add Binding。在右侧的 Path 下拉菜单中,应该能找到你的 "Phoenix SM600 Drone Controller" 或其下的具体控件(如 Left Stick [Phoenix SM600 Drone Controller], Button South [Phoenix SM600 Drone Controller])。将它们绑定到对应的 Action。
-
使用 Player Input Component: 在你的玩家或其他需要输入的 GameObject 上添加 Player Input 组件。将你的 Input Actions Asset 拖入 Actions 字段。设置 Behavior (如 Send Messages, Invoke Unity Events) 来响应输入。
-
或者通过 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设备冲突 |
RuntimeInitializeOnLoadMethod | Unity运行时自动调用设备注册方法(无需手动触发) |
常见问题解决方案
-
设备未识别
- 检查VID/PID是否与设备管理器中的值完全一致(注意十六进制大小写)
- 确保结构体
Size
与设备报告长度一致(可通过Wireshark抓包验证)
-
输入值始终为0
- 使用HIDAPI调试工具确认设备发送数据正常
- 检查
[FieldOffset]
偏移量是否与实际数据位置对齐
-
Unity崩溃或报错
- 确保项目设置中启用新版Input System:
Edit > Project Settings > Player > Active Input Handling → Both
- 更新Unity至2020.3+,并安装最新版Input System Package
- 确保项目设置中启用新版Input System:
最终验证
- 在场景中挂载
Phoenix6000Tester
脚本 - 运行Unity,操作凤凰6000模拟器的摇杆
- 控制台应实时输出X/Y轴数据