为 Unity 项目添加自定义 USB HID 设备支持 (适用于 PC 和 Android/VR)-任何手柄、无人机手柄、摇杆、方向盘
这是一份关于如何在 Unity 中为特定 USB HID 设备(如 Phoenix SM600 手柄)添加支持,并确保其在打包成 APK 安装到独立 VR 设备后仍能正常工作的教程。
目标: 使 Unity 能够识别并处理特定 USB HID(Human Interface Device)游戏手柄的输入,即使该设备没有被 Unity 的 Input System 默认支持。确保该支持在 PC 和打包后的 Android 应用(例如,安装在独立 VR 头显上)中均有效。
核心工具: Unity Input System 包。
背景: Unity 的 Input System 提供了强大的设备支持,但对于一些非标准或小众的 HID 设备,可能需要手动定义其数据布局并注册,以便系统能够正确解析其输入信号。
第 1 步:环境准备与设备识别
-
安装 Input System 包: 确保 Unity 项目已通过 Window > Package Manager 安装了 Input System 包。
-
获取设备标识符 (VID & PID):
-
将目标 USB HID 设备连接到 PC。
-
打开 Windows 的“设备管理器”。
-
找到该设备(可能在“人体学输入设备”或“通用串行总线控制器”下)。
-
右键点击设备,选择“属性”。
-
切换到“详细信息”选项卡。
-
在“属性”下拉菜单中选择“硬件 ID”。
-
记录下 VID_XXXX 和 PID_YYYY 中的十六进制数值(例如,VID_1781 和 PID_0898)。这是设备的唯一厂家 ID 和产品 ID。
-
第 2 步:定义设备输入报告结构 (Input Report Struct)
为了让 Input System 理解设备发送的原始数据流,需要定义一个 C# 结构体(struct)来精确映射数据包的内存布局。
-
创建 C# 脚本: 在 Unity 项目的 Assets 文件夹中创建一个新的 C# 脚本(例如 CustomHIDDeviceSupport.cs)。
-
定义结构体: 在脚本中,定义一个结构体,使用 StructLayout 属性指定精确的内存布局和大小,并使用 FieldOffset 指定每个数据字段在数据包中的字节偏移量。
-
映射原始字节 (初始阶段): 作为第一步,建议先将所有有意义的数据字节映射为原始 byte 类型的 InputControl。这有助于在 Input Debugger 中观察原始值,方便后续映射到具体的摇杆轴或按钮。
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using System.Runtime.InteropServices;// 定义设备发送的数据包结构
// [StructLayout(LayoutKind.Explicit, Size = N)] N 为数据包的总字节数
// : IInputStateTypeInfo 是 Input System 要求状态结构实现的接口
[StructLayout(LayoutKind.Explicit, Size = 9)] // 假设设备报告大小为 9 字节
public struct ExampleHIDInputReport : IInputStateTypeInfo
{// Report ID 通常是 HID 报告的第一个字节[FieldOffset(0)] public byte reportId;// 使用 [InputControl] 将数据字段标记为输入控件// layout = "Byte" 表示读取原始字节值 (0-255)// offset = X 指定该字节在结构体中的偏移量// name = "uniqueInternalName" 内部使用的控件名称// displayName = "User Friendly Name" 在 Input Debugger 中显示的名字[InputControl(name = "byte1Raw", layout = "Byte", offset = 1, displayName = "Data Byte 1")][FieldOffset(1)] public byte byte1_raw;[InputControl(name = "byte2Raw", layout = "Byte", offset = 2, displayName = "Data Byte 2")][FieldOffset(2)] public byte byte2_raw;// ... 为所有相关的数据字节添加类似的定义 ...[InputControl(name = "byte8Raw", layout = "Byte", offset = 8, displayName = "Data Byte 8")][FieldOffset(8)] public byte byte8_raw;// 实现 IInputStateTypeInfo 接口,指定数据格式为 HIDpublic FourCC format => new FourCC('H', 'I', 'D');
}
第 3 步:创建并注册设备布局 (Device Layout)
接下来,创建一个类来代表这个设备,并告诉 Input System 当匹配到特定 VID/PID 的设备时,应使用上面定义的结构体来解析其数据。
-
定义设备类: 在同一个 C# 脚本中,或另一个脚本中,创建一个继承自 InputDevice、Gamepad、Joystick 或 HID 的类。继承 Gamepad 或 Joystick 可以方便后续映射到标准控件。
-
添加 InputControlLayout 属性: 使用此属性标记该类,指定使用的状态结构体 (stateType) 和在 Input Debugger 中显示的名称 (displayName)。
-
实现静态构造函数: 在类中添加一个静态构造函数 (static ClassName())。这是注册布局的最佳位置,因为它会在类首次被访问时自动执行一次。
-
调用 InputSystem.RegisterLayout: 在静态构造函数中,调用 InputSystem.RegisterLayout。使用 InputDeviceMatcher 指定匹配条件:接口类型为 "HID",并提供正确的 vendorId 和 productId。
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.HID;
using UnityEngine.InputSystem.Utilities;
#if UNITY_EDITOR
using UnityEditor;
#endif// [InitializeOnLoad] 确保在编辑器启动时注册布局
#if UNITY_EDITOR
[InitializeOnLoad]
#endif
// [InputControlLayout] 关联状态结构体和显示名称
[InputControlLayout(stateType = typeof(ExampleHIDInputReport), displayName = "Custom USB HID Device (Raw)")]
public class CustomHIDController : Gamepad // 或 HID, InputDevice 等
{// 静态构造函数,用于注册布局static CustomHIDController(){// 使用 VID 和 PID 注册设备布局InputSystem.RegisterLayout<CustomHIDController>(matches: new InputDeviceMatcher().WithInterface("HID") // 必须是 HID 接口.WithCapability("vendorId", 0x1781) // 替换为实际的 Vendor ID.WithCapability("productId", 0x0898) // 替换为实际的 Product ID);// (可选) 在控制台输出日志确认注册成功// 只在编辑器模式下或开发版本中输出,避免干扰发布版本#if UNITY_EDITOR || DEVELOPMENT_BUILDDebug.Log($"Custom HID Controller layout registered for VID:0x1781 PID:0x0898.");#endif}// [RuntimeInitializeOnLoadMethod] 确保在游戏运行时也能触发注册// BeforeSceneLoad 保证在任何场景加载前完成注册[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]static void InitializeInPlayer(){// 该方法体可以为空。// 其存在和标记会确保静态构造函数在运行时被调用(如果尚未调用)。}// 后续步骤: 在 FinishSetup() 中可以将原始字节映射到标准化控件 (如 leftStick, buttons)// protected override void FinishSetup()// {// base.FinishSetup();// // 例如: leftStick = GetChildControl<StickControl>("leftStick");// // 需要在结构体中定义更复杂的 InputControl (如 Stick, Button) 并在此处获取它们// }
}
IGNORE_WHEN_COPYING_START
content_copydownload
Use code with caution. C#
IGNORE_WHEN_COPYING_END
重要: 此脚本无需挂载到任何游戏对象上。其包含的 [InitializeOnLoad] 和 [RuntimeInitializeOnLoadMethod] 属性会使其自动执行注册逻辑。
第 4 步:针对 Android/VR 平台的注意事项
将应用打包成 APK 并安装到独立 VR 设备(如 Meta Quest, Pico)时,要确保手柄正常工作,需要依赖以下条件:
-
VR 设备支持 USB OTG: 设备的 USB 端口必须支持 USB On-The-Go 功能,允许其作为主机连接外部 USB 设备。主流 VR 头显通常支持此功能。
-
Android 系统识别 HID 设备: VR 设备底层的 Android 操作系统需要能够识别插入的 USB 设备为标准的 HID 游戏手柄。对于大多数遵循 HID 规范的设备,Android 会自动处理。
-
[RuntimeInitializeOnLoadMethod] 的作用: 正如代码中所示,这个属性确保了即使在没有 Unity 编辑器的设备上运行游戏时,InitializeInPlayer 方法也会被调用,进而触发静态构造函数中的 InputSystem.RegisterLayout。这保证了自定义布局在 APK 运行时被注册。
-
VID/PID 准确性: 这是最关键的一点。如果在代码中提供的 VID 或 PID 与设备的实际值不符,Input System 将无法将设备与自定义布局匹配,导致设备可能被识别为通用 HID 设备或完全不被识别。
-
物理连接: 确保使用的 USB 线缆和任何必要的转接头(如 USB-A to USB-C)工作正常。
第 5 步:创建 Input Actions 并编写脚本读取输入
现在我们已经教会了 Unity 如何识别和理解我们的自定义 HID 设备(如 Phoenix SM600 手柄)发送的原始数据,接下来需要设置 Input Actions 并编写一个脚本来实际读取这些数据,并将其用于游戏逻辑。
-
创建 Input Actions Asset:
-
在 Unity 项目的 Assets 窗口中,右键点击 Create > Input Actions。
-
给这个新资源文件命名,例如 CustomControllerActions。
-
双击打开该资源文件,进入 Input Actions 编辑器。
-
-
定义 Action Map 和 Actions:
-
Action Maps: 在左侧面板点击 "+" 号添加一个新的 Action Map,命名为例如 Gameplay。Action Map 用于组织一组相关的操作(比如所有玩家控制的动作)。
-
Actions: 在中间面板为 Gameplay Action Map 添加 Actions。根据你的需求定义动作。基于你提供的脚本,我们可能需要读取至少两个轴的输入。**重要:**由于我们在第 2 步中只映射了原始字节,我们需要创建 Actions 来读取这些原始字节值。
-
点击 "+" 添加一个 Action,命名为例如 LeftStickVerticalRaw。
-
设置 Action Type 为 Value。
-
设置 Control Type 为 Axis (或者 Integer 如果你只想读取原始 0-255 值,但 Axis 通常更灵活用于后续处理)。
-
-
添加另一个 Action,命名为例如 LeftStickHorizontalRaw。
-
同样设置 Action Type 为 Value 和 Control Type 为 Axis。
-
-
(根据需要添加更多 Actions,例如对应右摇杆的原始字节 RightStickVerticalRaw, RightStickHorizontalRaw 等)
-
-
-
绑定 Actions 到自定义设备控件: 这是将抽象动作连接到具体设备输入的关键步骤。
-
选中 LeftStickVerticalRaw Action。
-
在右侧的 Properties 面板中,点击 Path 属性旁边的 "+" 号,选择 Add Binding。
-
在弹出的绑定窗口 (Listen / Path) 中,展开 HID 或你设备继承的类型(如 Gamepad)。
-
找到你的自定义设备布局名称(在第 3 步中 InputControlLayout 的 displayName 定义的,例如 "Custom USB HID Device (Raw)" 或 "Phoenix SM600 Drone Controller (Raw)")。
-
展开该设备,找到你在第 2 步中定义的对应摇杆垂直方向的原始字节控件(例如 Data Byte 4 或你在 displayName 里标记的 "左摇杆上下?" 对应的 byte4Raw)。选择这个原始字节控件。
-
对 LeftStickHorizontalRaw Action 重复此过程,将其绑定到代表左摇杆水平方向的原始字节控件(例如 Data Byte 5 或 byte5Raw)。
-
(为其他需要读取的原始字节 Action(如右摇杆)重复绑定过程)
-
完成后,点击 Input Actions 编辑器窗口顶部的 Save Asset 按钮。
-
-
编写或调整输入读取脚本: 现在我们使用一个脚本来引用并读取这些配置好的 Actions。以下是你提供的脚本的一个修正和解释版本,假设我们读取上面定义的 LeftStickVerticalRaw 和 LeftStickHorizontalRaw。
using UnityEngine; using UnityEngine.InputSystem; // 引入 Input System 命名空间// 脚本名称(建议更通用,如 CustomDeviceInputReader) public class CustomDeviceInputReader : MonoBehaviour {// 使用 [SerializeField] 在 Inspector 中关联 Action Reference// 这些变量将链接到 Input Actions Asset 中定义的 Action[SerializeField]private InputActionReference leftStickVerticalRawAction; // 关联 LeftStickVerticalRaw Action[SerializeField]private InputActionReference leftStickHorizontalRawAction; // 关联 LeftStickHorizontalRaw Action// --- 如果需要读取右摇杆,也添加对应的引用 ---// [SerializeField]// private InputActionReference rightStickVerticalRawAction;// [SerializeField]// private InputActionReference rightStickHorizontalRawAction;// 存储读取到的原始值 (可选,用于调试或复杂处理)private byte rawVerticalValue;private byte rawHorizontalValue;// 存储处理后的轴值 (-1 to +1 范围)private float processedVerticalAxis;private float processedHorizontalAxis;// Awake 在脚本对象被加载时调用void Awake(){// 检查 Inspector 中的引用是否已设置,给出明确错误提示if (leftStickVerticalRawAction == null || leftStickVerticalRawAction.action == null)Debug.LogError("Left Stick Vertical Raw Action Reference not set in Inspector.", this);if (leftStickHorizontalRawAction == null || leftStickHorizontalRawAction.action == null)Debug.LogError("Left Stick Horizontal Raw Action Reference not set in Inspector.", this);// ... (添加对其他 Action 引用的检查) ...}// OnEnable 在对象或组件启用时调用void OnEnable(){// 启用需要监听的 Action。Action 必须启用后才能读取值或触发事件。leftStickVerticalRawAction?.action.Enable(); // ?. 安全调用,避免空引用错误leftStickHorizontalRawAction?.action.Enable();// ... (启用其他 Actions) ...}// OnDisable 在对象或组件禁用时调用void OnDisable(){// 禁用 Action,释放资源,停止监听。leftStickVerticalRawAction?.action.Disable();leftStickHorizontalRawAction?.action.Disable();// ... (禁用其他 Actions) ...}// Update 每帧调用一次void Update(){// --- 读取原始字节值 ---// 如果 Action 绑定到 byte 类型的 InputControl,即使 Action Type 是 Axis,// ReadValue<byte>() 也可以直接读取原始 0-255 值。if (leftStickVerticalRawAction != null && leftStickVerticalRawAction.action != null){rawVerticalValue = leftStickVerticalRawAction.action.ReadValue<byte>();}if (leftStickHorizontalRawAction != null && leftStickHorizontalRawAction.action != null){rawHorizontalValue = leftStickHorizontalRawAction.action.ReadValue<byte>();}// ... (读取其他原始字节值) ...// --- 处理原始字节值 ---// **非常重要:** 原始字节 (0-255) 需要根据设备的具体行为进行解释。// 常见的处理方式是:假定 128 是中心静止位置。// 小于 128 是一个方向,大于 128 是另一个方向。// 将其标准化到 -1 到 +1 的范围,方便用于游戏逻辑。// 注意:某些设备可能使用 0 作为起始点,或有不同的中心值和范围,需要根据实际情况调整!processedVerticalAxis = NormalizeByteAxis(rawVerticalValue);processedHorizontalAxis = NormalizeByteAxis(rawHorizontalValue);// ... (处理其他轴的原始值) ...// --- 输出调试信息 (可选) ---// 使用 F2 格式化输出,保留两位小数Debug.Log($"Raw Left Stick - V: {rawVerticalValue}, H: {rawHorizontalValue}");Debug.Log($"Processed Left Stick - V: {processedVerticalAxis:F2}, H: {processedHorizontalAxis:F2}");// ... (输出其他轴的值) ...// --- 在这里使用处理后的轴值控制游戏对象 ---// 例如,控制无人机的移动:// Vector3 movement = new Vector3(processedHorizontalAxis, 0, processedVerticalAxis);// transform.Translate(movement * Time.deltaTime * speed);// (具体逻辑取决于你的游戏需求)}// 辅助函数:将 0-255 的字节值标准化到大约 -1 到 +1 的范围// (假设 128 为中心点)private float NormalizeByteAxis(byte rawValue){// 将 byte (0-255) 转换为 floatfloat floatValue = rawValue;// 计算偏离中心 (128) 的值 (-128 to 127)float deviation = floatValue - 128f;// 标准化到 -1.0 到 ~1.0 的范围// 如果 deviation >= 0, 除以 127 (128 -> 0, 255 -> 1)// 如果 deviation < 0, 除以 128 (-128 -> -1, 0 -> 0)if (deviation >= 0){return deviation / 127f; // Max positive deviation is 255-128 = 127}else{return deviation / 128f; // Max negative deviation is 0-128 = -128}// // 简化版(假设正负范围对称,结果略有不同):// return (rawValue - 128f) / 128f;}// 注意: 使用 InputActionReference 时,Input System 通常会自动处理资源的生命周期,// 手动调用 Dispose 一般不是必需的,除非在非常特定的高级场景下。 }
-
将脚本添加到场景并配置:
-
在 Unity 场景中创建一个空的游戏对象(GameObject),或者选择一个你想用来处理输入的现有对象。
-
将上面编写的 CustomDeviceInputReader.cs 脚本拖拽到这个游戏对象的 Inspector 面板上。
-
你会看到脚本组件上有 Left Stick Vertical Raw Action 和 Left Stick Horizontal Raw Action 两个字段(以及你可能添加的其他字段)。
-
点击每个字段旁边的圆形图标,或者直接将你在 CustomControllerActions 资源文件中定义的相应 Action(例如 Gameplay/LeftStickVerticalRaw)拖拽到对应的字段上。
-
确保这个挂载了脚本的游戏对象在场景中是激活(Active)的。
-
-
运行与测试:
-
连接你的自定义 HID 设备。
-
运行 Unity 场景。
-
观察 Console 窗口的输出。当你移动手柄的左摇杆时,你应该能看到 Raw Left Stick 的字节值 (0-255) 和 Processed Left Stick 的标准化值 (-1 to +1) 相应地变化。
-
根据输出调整 NormalizeByteAxis 函数中的逻辑(特别是中心值 128 和除数 127/128),以确保静止时轴值接近 0,推到极限时接近 -1 或 +1。
-
下一步/优化:
-
直接映射标准控件: 如果你确定了哪些原始字节对应标准的游戏手柄控件(如左摇杆、右摇杆、按钮),可以回到第 2 步和第 3 步,修改设备布局 (struct 和 class)。使用 Input System 提供的更高级的 InputControl 布局(如 StickControl, ButtonControl),并在 FinishSetup() 方法中将原始字节数据处理后映射到这些标准控件上。这样做的好处是,你的 Input Actions 可以直接绑定到标准的 leftStick, rightStick, buttonSouth 等路径,使输入配置更通用,读取脚本也更简单(可以直接 ReadValue<Vector2>() 获取摇杆值)。但这需要对设备的数据格式有更深入的理解。
-
处理按钮: 按钮通常隐藏在某个字节的特定位(bit)中。需要使用位运算(如 & 按位与)来检查特定位是否为 1,以判断按钮是否按下。同样可以在设备布局中定义 ButtonControl 并进行映射。
现在,你应该拥有一个完整的流程:从识别未知 HID 设备、定义其数据布局、在 Unity Input System 中注册它,到最后通过 Input Actions 读取其(目前是原始的)输入值并在游戏中使用。
第 6 步:验证与调试
-
Unity 编辑器测试: 在编辑器中连接设备,打开 Window > Analysis > Input Debugger。检查 Devices 列表中是否出现了你的自定义设备(使用 displayName 标识)。选中它,观察右侧面板中定义的控件(如 byte1Raw 等)的值是否随手柄操作而变化。
-
PC Build 测试: 打包一个 PC 版本,确认设备在独立构建中也能正常工作。
-
Android/VR 设备测试:
-
将 APK 安装到 VR 设备。
-
连接手柄。
-
运行游戏,测试手柄输入是否符合预期。
-
如果无效:
-
确认 VR 设备系统层面是否识别手柄(例如,在系统设置或某些原生应用中测试)。
-
使用 adb logcat 查看设备日志,搜索与 Input System、HID 或你的设备 VID/PID 相关的错误或信息。
-
尝试远程连接 Unity Profiler 和 Input Debugger 到运行在 VR 设备上的开发版本,以进行更深入的调试。
-
-
结论: 通过精确定义 HID 设备的输入报告结构,并使用正确的 VID/PID 在 Input System 中注册自定义布局,可以实现对特定 USB HID 设备的支持。利用 [RuntimeInitializeOnLoadMethod] 确保该注册过程在最终打包的应用程序(包括 Android/VR APK)中也能自动执行,从而使自定义手柄能够在不同平台上一致地工作,前提是底层操作系统能够识别该 HID 设备。
相关文章:
为 Unity 项目添加自定义 USB HID 设备支持 (适用于 PC 和 Android/VR)-任何手柄、无人机手柄、摇杆、方向盘
这是一份关于如何在 Unity 中为特定 USB HID 设备(如 Phoenix SM600 手柄)添加支持,并确保其在打包成 APK 安装到独立 VR 设备后仍能正常工作的教程。 目标: 使 Unity 能够识别并处理特定 USB HID(Human Interface Device&#x…...
恒流源电路
常见的是上面这几种, 运放恒流电路一般搭配三极管使用 比赛用的模块可以用这种,会准一点...
python2反编译部分
文章目录 1、所需环境2、确认打包工具(没成功)3、 解包.exe文件(以PyInstaller为例) - useful【***总的来说这一步对我有用】4、定位关键文件 - useful5、 修复.pyc文件头(关键步骤!)- maybe-ig…...
Selenium3自动化测试,Python3测试开发教程视频测试用例设计
Selenium3自动化测试,Python3测试开发教程视频测试用例设计25套高级软件测试,性能测试,功能测试,自动化测试,接口测试,移动端测试,手机测试,WEB测试,渗透测试,…...
PyTorch 2.0编译器技术深度解析:如何自动生成高性能CUDA代码
引言:编译革命的范式转移 PyTorch 2.0的torch.compile不仅是简单的即时编译器(JIT),更标志着深度学习框架从解释执行到编译优化的范式跃迁。本文通过逆向工程编译过程,揭示PyTorch如何将动态图转换为高性能CU…...
ctfshow web入门 web44
信息收集 依旧是把所有输出丢弃,这一次多了flag的过滤,没啥好说的,用*或者?代替就可以了 if(isset($_GET[c])){$c$_GET[c];if(!preg_match("/;|cat|flag/i", $c)){system($c." >/dev/null 2>&1");} }else{h…...
三生原理的离散生成逻辑如何与复分析结合?
AI辅助创作: 三生原理离散生成逻辑与复分析结合路径分析 一、生成规则与解析延拓的协同 参数化联动机制向复数域延伸 三生原理的离散素数生成公式(如p=3(2n+1)+2(2n+m+1))通过引入复数参数 n,m∈C,可扩展为复平面上的解析函数,从而建立与黎曼ζ函数的关联通道。…...
数据升降级:医疗数据的“时空穿梭“系统工程(分析与架构篇)
一、核心挑战与量化分析 1. 版本演化困境的深度解析 (1) 格式断层的结构化危机 数据转换黑洞:某医疗信息平台(2021-2023)统计显示: 数据类型CDA R1→R2转换失败率R2→FHIR转换失败率关键失败点诊断记录28.4%19.7%ICD编码版本冲突(18.7%)用药记录15.2%12.3%剂量单位标准化…...
简单句练习--语法基础
文章目录 谓语和非谓语及物与不及物动词及物不及物主语必须由名词性质的成分充当谓语和非谓语 与中文不同,英语中的动词分为谓语形式和非谓语形式。 以“do”为例, 可以充当谓语的形式有:do,does,did, 以及其他各种时态,如:have done,is doing等。不可独立充当谓语的有…...
基于若依RuoYi-Vue3-FastAPI 的 Docker 部署记录
最近在参与导师项目开发过程中,我选择基于若依 FastAPI Vue3 模板作为系统框架,通过 Docker 实现前后端、数据库和缓存环境的容器化部署。 RuoYi-Vue3-FastAPI的github地址:https://github.com/insistence/RuoYi-Vue3-FastAPI 🛠…...
基于开源AI智能名片链动2+1模式S2B2C商城小程序的电商直播流量转化路径研究
摘要:在电商直播单场GMV突破2.28亿元的流量狂欢背后,传统直播模式正面临"流量过载而转化低效"的困境。本文提出以开源AI智能名片链动21模式S2B2C商城小程序重构流量转化路径,通过智能内容引擎、动态激励体系、供应链协同三大技术模…...
【Linux系统】Linux进程信号(产生,保存信号)
1. 信号快速认识 1-1 基本结论 如何识别信号?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。信号产生之后,是知道怎么处理的,同理,如果信号没有产生,也是知道怎么处理信号的。所以…...
llamafactory-cli webui启动报错TypeError: argument of type ‘bool‘ is not iterable
一、问题 在阿里云NoteBook上启动llamafactory-cli webui报错TypeError: argument of type ‘bool’ is not iterable This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run gradio deploy from the terminal in the working directory t…...
工 厂 模 式
冷知识,当我们需要使用平底锅时,我们并不需要知道平底锅是怎么造的,坏了只需要再买就好了。至于造平底锅,全部交给我们的生产工厂就好。 蕴含这种创建对象思路的设计方法,我们称为“工厂模式”。 核心思想 工厂模式&…...
synchronized与Lock深度对比
Java并发编程:synchronized与Lock深度对比 基本概念 1.1 synchronized synchronized是Java内置的关键字,属于JVM层面的锁机制。它通过对象监视器(Monitor)实现同步,具有自动获取和释放锁的特性。 // 同步方法 public synchronized void sy…...
LeetCode —— 94. 二叉树的中序遍历
94. 二叉树的中序遍历 题目:94. 二叉树的中序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) :…...
【无标题】四色拓扑收缩模型中环形套嵌结构的颜色保真确定方法
#### **1. 环形嵌套结构的局部保真机制** - **零点虚边与环形嵌套**:在顶点 \( v \) 处引入环形嵌套结构(如环面 \( T^2 \)),通过虚边连接形成闭合路径。该结构作为“颜色记忆单元”,存储相邻区域的色彩信息࿰…...
Curl 全面使用指南
Curl(Client URL)是一个跨平台命令行工具,支持多种协议(HTTP/HTTPS/FTP/SFTP等),用于数据传输、API调试、文件上传/下载等场景。以下从 核心功能、用户疑问解答、高级技巧 三方面系统总结,并整合…...
vscode 的空格和 tab 设置 与 Rime 自建词库
自动保存(多用于失去焦点时保存) Files: Auto Save 推荐不勾 保存时格式化(Pritter 插件的功能,自动使用 Pritter 的格式) Editor: Format On Save 推荐不勾 tab 的空格数量,2 或 4 Editor: Tab Size 推荐…...
Spark-小练试刀
任务1:HDFS上有三份文件,分别为student.txt(学生信息表)result_bigdata.txt(大数据基础成绩表), result_math.txt(数学成绩表)。 加载student.txt为名称为student的RDD…...
Python爬虫实战:获取jd商城最新5060ti 16g显卡销量排行榜商品数据并做分析,为显卡选购做参考
一、引言 1.1 研究目的 本研究旨在利用 Python 爬虫技术,从京东商城获取 “5060ti 16g” 型号显卡的商品数据,并对这些数据进行深入分析。具体目标包括: 实现京东商城的模拟登录,突破登录验证机制,获取登录后的访问权限。高效稳定地爬取按销量排名前 20 的 “5060ti 16g…...
【Vue bug】:deep()失效
vue 组件中使用了 element-plus 组件 <template><el-dialog:model-value"visible":title"title":width"width px":before-close"onClose"><div class"container" :style"{height:height px}"&g…...
基于数字图像处理的裂缝检测与识别系统(Matlab)
【优化】Matlab裂缝检测与识别系统 基于数字图像处理的裂缝检测与识别系统(Matlab) (基本常在线秒回,有兴趣可以随时联系博主) 系统主要的内容包括: 1.图像加载与初始化 选择图像文件并加载:…...
day12:遗传算法及常见优化算法分享
遗传算法这些常见优化算法简直是 “宝藏素材”!用好了,轻轻松松就能填满论文一整节内容;要是研究透彻,甚至能独立撑起一整个章节。今天不打算深入展开,有个基础认知就行。等之后写论文真要用到这些算法了,咱…...
【计算机视觉】语义分割:MMSegmentation:OpenMMLab开源语义分割框架实战指南
深度解析MMSegmentation:OpenMMLab开源语义分割框架实战指南 技术架构与设计哲学系统架构概览核心技术特性 环境配置与安装指南硬件配置建议详细安装步骤环境验证 实战全流程解析1. 数据集准备2. 配置文件定制3. 模型训练与优化4. 模型评估与推理 核心功能扩展1. 自…...
25_04_30Linux架构篇、第1章_02源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62
Linux_基础篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62 版本号: 1.0,0 作者: 老王要学习 日期: 2025.05.01 适用环境: Centos7 文档说明 本文…...
【重走C++学习之路】25、特殊类设计
目录 一、不能被拷贝的类 二、堆上创建对象的类 三、栈上创建对象的类 四、不能被继承的类 五、单例模式 结语 一、不能被拷贝的类 如何实现一个不能被拷贝的类?在看到这个要求的第一反应就是禁掉类的拷贝构造函数和赋值运算符重载函数,再往深了探…...
基于Redis实现-用户签到
基于Redis实现-用户签到 这个功能将使用到Redis中的BitMap来实现。 我们按照月来统计用户签到信息,签到记录为1,未签到则记录为0 把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路称为位图(BitMap)。…...
利用Redisson分布式锁解决多服务器数据刷新问题
利用Redisson分布式锁解决多服务器数据刷新问题 一、业务背景二、代码实现1、引入Redisson依赖2、配置Redisson,实际项目中Redis为集群配置3、自定义拒绝策略4、异步刷新网元服务 三、项目结构及源码 一、业务背景 最近有个需求需要自动刷新网元服务,由…...
25.4.30数据结构|并查集 路径压缩
前言 在QuickUnion快速合并的过程中,每次都要找根ID,而路径压缩让找根ID变得更加迅速直接。 路径压缩 针对的是findRootIndex()【查找根ID】进行的压缩。 需要实现的是: 在找根节点的过程中,记录这条路径上的所有信息,…...
react学习笔记3——基于React脚手架
React路由 相关理解 SPA的理解 单页Web应用(single page web application,SPA)。整个应用只有一个完整的页面。点击页面中的链接不会刷新页面,只会做页面的局部更新。数据都需要通过ajax请求获取, 并在前端异步展现。 路由的理…...
C#中的LINQ:简化数据查询与操作
引言 在现代软件开发中,处理和操作数据是不可避免的任务。无论是从数据库读取信息,还是对内存中的集合进行筛选、排序等操作,开发者都需要一种高效且易于使用的方法。C#中的LINQ(Language Integrated Query)正是为此而…...
OkHttp3.X 工具类封装:链式调用,支持HTTPS、重试、文件上传【内含常用设计模式设计示例】
OkHttp3.X 工具类封装:链式调用,支持HTTPS、重试、文件上传 基于OkHttp3.X封装,提供链式调用API,简化GET/POST请求,支持HTTPS、自动重试、文件上传等功能,提升开发效率。 在 Android 和 Java 开发中&#x…...
Unity SpriteEditor(精灵图片编辑器)
🏆 个人愚见,没事写写笔记 🏆《博客内容》:Unity3D开发内容 🏆🎉欢迎 👍点赞✍评论⭐收藏 🔎SpriteEditor: 精灵图片编辑器 📌用于编辑2D游戏开发中使用的Sp…...
雅思写作--70个高频表达
文章目录 1. learn new skills学生通过户外活动学到很多新技2. take immediate action to do各国采取有效行动以保护环境政府采取了必要行动以减少失业。你应该立即采取行动来解3. communication skills4. grow significantly5. have many advantages1. learn new skills “lea…...
Anaconda中配置Pyspark的Spark开发环境
Anaconda中配置Pyspark的Spark开发环境 目录 1.在控制台中测试ipython是否启动正常2.安装好Java3.安装Spark并配置环境变量4.PySpark配置5.修改spark\conf下的spark-env文件6.测试Pyspark是否安装成功 1.在控制台中测试ipython是否启动正常 anaconda正常安装 这里先检查ipyt…...
Spring 提供了多种依赖注入的方式
构造器注入(Constructor Injection) 构造器注入是通过类的构造函数来注入依赖项。这是 Spring 推荐的方式,因为它提供了不可变性和更好的可测试性。 import org.springframework.stereotype.Component;Component public class ServiceA {pub…...
面经-计算机网络——OSI七层模型与TCP/IP四层模型的对比详解
OSI七层模型与TCP/IP四层模型的对比详解 一、图示解析:分层封装结构 你提供的图清晰展示了网络通信中从应用层到物理层的封装过程,每一层都会对上层的数据加上自己的头部信息(Header): 应用层: 应用…...
网络安全知识问答微信小程序的设计与实现
网络安全知识问答微信小程序的设计与实现,说白了,就是搭建一款网络安全知识问答微信小程序,类似网络安全百科直通车。三步走。 需求沟通 进行需求沟通,此处省略1000字。 画草图 根据沟通的需求,进行整理,…...
Canvas特效实例:黑客帝国-字母矩阵(字母雨)
黑客帝国-字幕矩阵(字母雨) 效果预览代码实现思路解析遗留问题 效果预览 话不多说,我们直接上效果:当页面加载完成,屏幕上会落下如瀑布般的绿色字母流,不断向下滑动,仿佛进入了黑客帝国的数字世…...
「Mac畅玩AIGC与多模态11」开发篇07 - 使用自定义名言插件开发智能体应用
一、概述 本篇介绍如何在 macOS 环境下,通过编写自定义 OpenAPI Schema,将无需认证的名言服务接入 Dify 平台,并开发基于外部公共数据的智能体应用。本案例继续实践 GET 请求型 API 的实际调用技巧。 二、环境准备 1. 确认本地开发环境 macOS 系统Dify 平台已部署并可访问…...
快速上手非关系型数据库-MongoDB
简介 MongoDB 是一个基于文档的 NoSQL 数据库,由 MongoDB Inc. 开发。 NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。 MongoDB 的设计理念是为了应对大数据量、…...
响应式布局,在飞帆平台中如此简单
这些控件都是可以自己动手去实现的。也可以将这些控件复制到自己名下进行修改。 响应式布局https://fvi.cn/782...
UN R79 关于车辆转向装置形式认证的统一规定(正文部分1)
UN R79法规是针对转向装置的型式认证法规,涉及A/B1/C类的横向控制辅助驾驶功能,对各功能的功能边界、性能要求、状态提示、故障警示以及型式认证要提交的信息做了规范,本文结合百度文心一言对法规进行翻译,并结合个人理解对部分内…...
深度学习系统学习系列【1】之基本知识
文章目录 说明基础知识人工智能、机器学习、深度学习的关系机器学习传统机器学习的缺陷选择深度学习的原因深度学习的关键问题深度学习的应用深度学习的加速硬件GPU环境搭建主流深度学习框架对比 说明 文章属于个人学习笔记内容,仅供学习和交流。内容参考深度学习原…...
python3GUI--视频监控管理平台 By:PyQt5(详细讲解)
文章目录 一.前言二.相关知识1.PyQt52.RTSP协议📌 简介:🧩 特点:📡 工作方式: 2. **RTMP(Real-Time Messaging Protocol)**📌 简介:&a…...
第十一届蓝桥杯 2020 C/C++组 既约分数
目录 题目: 题目描述: 题目链接: 思路: 核心思路: 思路详解: 代码: 代码详解: 题目: 题目描述: 题目链接: 既约分数 - 蓝桥云课 思路&a…...
如何让Steam下载速度解除封印?!
平时一直没注意到家里的路由器在偷懒。最近成功榨干家里的带宽,把平时一直20mb/s左右下载速度的路由器一番改造后成功steam下载速度稳定85Mb/s。平时一直都只发挥了他的1/3不到,真是太可惜了。 硬件 首先检查硬件,就千兆路由器而言…...
HOOK上瘾思维模型——AI与思维模型【88】
一、定义 HOOK上瘾思维模型是一种通过设计一系列的触发(Trigger)、行动(Action)、奖励(Reward)和投入(Investment)环节,来促使用户形成习惯并持续使用产品或服务的思维框…...
基于开源AI智能名片链动2+1模式S2B2C商城小程序的IP开发泡沫破局与价值重构研究
摘要:当前IP开发领域普遍存在"冒进式泡沫"现象,企业将初级IP包装为超级IP运营,导致资源错配与价值虚化。本文通过实证分析开源AI智能名片链动21模式S2B2C商城小程序的技术架构与商业逻辑,揭示其通过智能内容引擎、合规化…...