最近在做毕业设计,涉及到HID设备接入unity。
HID设备配置
当你想用单片机自己开发一个HID手柄时,网上可以找到比较多的教程,这里有一个大佬出的stm32的教程
使用 cubeMX软件配置游戏控制器_哔哩哔哩_bilibili
我是用stm32做的,当你成功配置手柄接入windows后,控制面板可以看到你的设备

编辑切换为居中
添加图片注释,不超过 140 字(可选)
这是我的设备,我们先打开看看

编辑
添加图片注释,不超过 140 字(可选)
这里我的报告描述符自定义手柄只有两个轴,就是x和y两个轴
轻轻滑动自定义手柄传感器可以看到两个轴的输入是正常的,到此为止我们的自定义HID手柄配置完成,下面我们看看怎样把它接入unity
unity接入
HID设备已经配置好了,按道理来说我们可以在unity里面直接使用,是的用unity自代的inputManager(old)确实可以使用我们的手柄进行输入,不需要任何配置。
但是有时候许多特定功能需要用到input system来配置我们的手柄进行操作。这时问题就出现了,unity并不认识你自定义的手柄。就像下图
编辑切换为居中
添加图片注释,不超过 140 字(可选)
这里如果你配置的手柄HID报告描述符被unity所识别那就不需要其他配置。但是如果你的HID设备不被unity所识别那就需要自己添加为HID设备。
再input system找了好久,这里是一篇添加的教程(之后我称之为官网教程):
HID Support | Input System | 1.0.2
由于涉及过多偏底层的开发,这里我和一位热心网友费好大劲才把他做好。这里我尽量用做通俗的语言把过程说清楚,这里由于每个人的HID配置都不一样,所以没有现成的代码可以给。
首先我需要识别我的设备为inputsystem所支持的设备,所以我新建一个类,继承自GamePad(inputsystem里面的输入类)
[InputControlLayout(stateType = typeof(DualShock4HIDInputReport)] public DualShock4GamepadHID : Gamepad { }
这里怎样识别自己的HID设备呢?类上边的这句代码为我们的设备指定了布局
官网给出了相应的模板:
[InputControlLayout(stateType = typeof(DualShock4HIDInputReport)]
#if UNITY_EDITOR
[InitializeOnLoad] // Make sure static constructor is called during startup.
#endif
public DualShock4GamepadHID : Gamepad
{
static DualShock4GamepadHID()
{
// This is one way to match the Device.
InputSystem.RegisterLayout<DualShock4GamepadHID>(
new InputDeviceMatcher()
.WithInterface("HID")
.WithManufacturer("Sony.+Entertainment")
.WithProduct("Wireless Controller"));
// Alternatively, you can also match by PID and VID, which is generally
// more reliable for HIDs.
InputSystem.RegisterLayout<DualShock4GamepadHID>(
matches: new InputDeviceMatcher()
.WithInterface("HID")
.WithCapability("vendorId", 0x54C) // Sony Entertainment.
.WithCapability("productId", 0x9CC)); // Wireless controller.
}
// In the Player, to trigger the calling of the static constructor,
// create an empty method annotated with RuntimeInitializeOnLoadMethod.
[RuntimeInitializeOnLoadMethod]
static void Init() {}
}
它里面带了两种识别方式一个是产品编号和厂家
.WithManufacturer("Sony.+Entertainment") .WithProduct("Wireless Controller"));,
一个是HID设备的PID和VID
// Alternatively, you can also match by PID and VID, which is generally
// more reliable for HIDs.
InputSystem.RegisterLayout<DualShock4GamepadHID>(
matches: new InputDeviceMatcher()
.WithInterface("HID")
.WithCapability("vendorId", 0x54C) // Sony Entertainment.
.WithCapability("productId", 0x9CC)); // Wireless controller.
更改其中的数据为自己的手柄配置,如果觉得麻烦就只保留PID和VID的识别
把上边的东西放在自己的代码里面你已经可以成功把你的HID设备添加为inputsystem支持的设备了,对,没错,现在你已经可以用inputsystem向HID设备发送信息了,具体操作看我这篇文章
unity与HID输入设备通讯,手柄控制器发聩功能实现 - 知乎
那么可以输入了吗??这时我们打开input debug窗口

编辑切换为居中
添加图片注释,不超过 140 字(可选)
发现不管怎么输入,这里数据依然是0。
这是因我们还没有写自己的手柄布局,简单来说就是接收报文的数据结构我们还没有告诉unity它不知道怎么显示就给你显示了gamePad类默认的手柄布局,之前的自定义的HID类上边的这行代码便是为我们指定我们手柄的布局的:
[InputControlLayout(stateType =typeof(DualShock4HIDInputReport)]//就是这一行 //DualShock4HIDInputReport是我们的自定义布局
public DualShock4GamepadHID : Gamepad {}
下面让我们来写一下自己的手柄布局吧:
我以自己举例子我只有X和Y两个轴,报告描述的数据结构是:
struct HIDReport {
byte reportId; // #0
byte leftStickX; // #1
byte leftStickY; // #2
}
由此我的布局写为:
[StructLayout(LayoutKind.Explicit, Size = 32)]
struct DualShock4HIDInputReport : IInputStateTypeInfo
{
public FourCC format => new FourCC('H', 'I', 'D');
[FieldOffset(0)] public byte reportId;
[InputControl(name = "leftStick", layout = "Stick", format = "VC2B")]
[InputControl(name = "leftStick/x", offset = 0, format = "BYTE",
parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
[InputControl(name = "leftStick/left", offset = 0, format = "BYTE",
parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
[InputControl(name = "leftStick/right", offset = 0, format = "BYTE",
parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1")]
[InputControl(name = "leftStick/y", offset = 1, format = "BYTE",
parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
[InputControl(name = "leftStick/up", offset = 1, format = "BYTE",
parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
[InputControl(name = "leftStick/down", offset = 1, format = "BYTE",
parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert=false")]
[FieldOffset(1)] public byte leftStickX;
[FieldOffset(2)] public byte leftStickY;
[InputControl(name = "rightStick", layout = "Stick", format = "VC2B")]
[InputControl(name = "rightStick/x", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
[InputControl(name = "rightStick/left", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
[InputControl(name = "rightStick/right", offset = 0, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1")]
[InputControl(name = "rightStick/y", offset = 1, format = "BYTE", parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
[InputControl(name = "rightStick/up", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
[InputControl(name = "rightStick/down", offset = 1, format = "BYTE", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert=false")]
[FieldOffset(3)] public byte rightStickX;
[FieldOffset(4)] public byte rightStickY;
}
要想写其他布局可以去官网教程看看哪个例子
这次再打开input Debug就可以看到我的数据可以输入进去了:

编辑切换为居中
添加图片注释,不超过 140 字(可选)
这里有可能你的布局出现的还不是自己写的。那么有可能是你自己的设备继承鱼GamePad类于是它用了GamePad的默认布局,解决方法也简单你继承自InputDevice类就可以了,这是我的全部实现
using UnityEngine;
using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Layouts;
using UnityEditor;
#if UNITY_EDITOR
[InitializeOnLoad] // Make sure static constructor is called during startup.
#endif
[InputControlLayout(stateType = typeof(PingGameJoy_USB_HID_Report), isGenericTypeOfDevice = true)]
//[DefaultMember("Item")]
//[InputControlLayout(stateType = typeof(GamepadState), isGenericTypeOfDevice = true)]
//[Preserve]
public class MyPad : InputDevice
{
static MyPad()
{
InputSystem.RegisterLayout<MyPad>(
matches: new InputDeviceMatcher()
.WithInterface("HID")
.WithCapability("vendorId", 1126) // Sony Entertainment.
.WithCapability("productId", 11346)); // Wireless controller.
}
// create an empty method annotated with RuntimeInitializeOnLoadMethod.
[RuntimeInitializeOnLoadMethod]
static void Init()
{ }
public static MyPad current { get; private set; }
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
}
[StructLayout(LayoutKind.Explicit, Size = 3)]
public struct PingGameJoy_USB_HID_Report : IInputStateTypeInfo
{
public FourCC format => new FourCC('H', 'I', 'D');
[FieldOffset(0)] public byte reportId;
[InputControl(name = "stick", format = "VC2B", layout = "Stick", displayName = "Main Stick")]
[InputControl(name = "stick/x", defaultState = 127, format = "BYTE",
offset = 0,
parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
[FieldOffset(1)] public byte x;
[InputControl(name = "stick/y", defaultState = 127, format = "BYTE",
offset = 1,
parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
// The stick up/down/left/right buttons automatically use the state set up for X
// and Y but they have their own parameters. Thus we need to also sync them to
// the parameter settings we need for our BYTE setup.
// NOTE: This is a shortcoming in the current layout system that cannot yet correctly
// merge parameters. Will be fixed in a future version.
[InputControl(name = "stick/up", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=2,clampMin=0,clampMax=1")]
[InputControl(name = "stick/down", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=2,clampMin=-1,clampMax=0,invert")]
[InputControl(name = "stick/left", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=2,clampMin=-1,clampMax=0,invert")]
[InputControl(name = "stick/right", parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp=2,clampMin=0,clampMax=1")]
[FieldOffset(2)] public byte y;
}
这样自己的设备就是标准的输入设备啦。可以输入也可以作交互反馈,反馈功能见我另一篇文章:
unity与HID输入设备通讯,手柄控制器发聩功能实现 - 知乎
希望我讲清楚了这个问题。若您有啥不解可以给我留言,B站吧
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/