这学期做的 VR 大作业,想使用 Leap Motion 提供的手势识别进行操控,但是没有各种 Headset 可以用(对,说的就是你俩 HTC 和 Oculus)。然而人家 Leap Motion 目前只提供 PC 和上面两兄弟的支持。那我们只有 Cardboard 怎么办呢?这里稍微记录一下。
我们在 PC 上利用 Leap Motion SDK 获取手势数据,然后传给 Android。传的方式参考了这里的思路:用 USB 将 PC 与 Android 连接起来,然后在 PC 端用 adb 建立连接后,Android 只需作为服务器监听 localhost 的一个端口,同时 PC 端作为客户端同样访问 localhost 的一个端口传数据即可。
目前 PC 端使用 Python 语言实现,Android 端使用 Unity 即 C# 语言实现。注意这里的 Unity 没有 Leap Motion 支持。因而它获取手势数据完全依赖于 PC 端的传入。如何选择适当的数据传入,并重现手的模型看起来是一个问题。
用户体验方面,我们在某宝上买了一个 Leap Motion 支架,它本来是为两个 Headset 准备的,但我们将其如法炮制到 Cardboard 上。目前几可以假乱真。
基于 USB+Unity 实现 Android 与 PC 通信
我们这里先实现上述任务的一个简单版本,实现 Android 与 PC 的通信即可。
我们基于这份代码进行改编。由于这里重点不在于 Socket 的使用,而且我也没搞懂,因此并不打算详细介绍。
Android Server
在 Unity 新建一个 EmptyObject 名为 Server 并添加名为 ServerController 的脚本如下:
1 | using System; |
这里是利用 Socket 实现了一个典型的监听在 127.0.0.1:8888 上的异步 TCP 服务器放在 Android 端。我们将其初始化流程放在 Start 中。希望 Unity 能与 C# 原版的 Socket 友好相处 ^_^ 。
我们只需实现两个回调函数 AcceptCallBack, ReceiveCallBack 即可。在接收到客户端发来的信息之后将其用 log 输出到 Unity 。
PC client
接下来是 PC 客户端。
1 | using System; |
简单的 TCP 客户端,连接到服务器 127.0.0.1:7777 。两个端口号是由下面的 adb forward 命令来设定的。这里为了方便直接写死。
流程
使用 USB 线连接 Android 和 PC ,打开调试模式
使用 adb 输入在命令行中输入
1
adb forward tcp:7777 tcp:8888
使用 adb 监控 Android 的调试信息,我们只关注 Unity 的输出
1
adb logcat -s Unity
在 Android 上运行 Unity 程序
在 PC 上运行客户端
我们惊喜的看到,Android server 已经收到了 PC client 传过来的消息!
Json 解析
我们已经将 IDE 换成了 Rider ,就很舒服,可以直接用 Nuget 安装 Json.NET 了。
从工具栏中的 Tools -> NuGet -> Manage Nuget Packages For Solution ,随后直接安装最热门的 Newtonsoft.JSON 即可。
使用这里的代码 ,解析功能正常工作。
Multithreading in Unity
今天一整天都在纠结于 Unity 的线程安全问题(以及看一些奇怪的东西,虽然我已经将他们全部删掉了)
找了很多资料,被大家一致认同的说法是:
Unity 在有些地方会使用多线程充分利用多核 CPU
但是这个细节并不暴露给开发者,你所写的那些基于 Event System 被 Unity 在合适的时机调用的代码全部在 Main thread 上运行
比如 Update, FixedUpdate 那些…
由于 Unity 是这样使用这些你写的代码的,因此你所能够在代码中调用到的 Unity API 都不是线程安全的,甚至为了保证安全性,如果你另开一个线程在其中比方说修改一个 GameObject 的 Position ,Unity 都会抛出一个 Exception
因此对开发者来说应该可以认为 Unity 所做的事情仅仅是单线程的。
后来我将上面的代码改了一下,在异步回调接收到客户端传来的消息后将一个 Text Label 的文字修改,但是并未触发 Exception 。(后来修改一个3D Text 的文字就闪退了…说明我们还是别在这里做一些危险的事情)
今天看了一下异步的机制,在这里比较详细的介绍了异步的底层实现。作者的主要观点是在异步机制中,并不会多出一个“牺牲品”线程被阻塞。而是通过中断,由 OS 来提醒一个 thread pool 线程去调用异步回调函数。不知道中间又经历了怎样复杂的过程,从结果来看,Unity 认为异步回调函数依旧是在 Main thread 上运行的。
我们暂时忽略 Unity 的 Job System 那一套理论,而是绝不吝惜开新线程,反正这种规模又不会带来什么上下文切换开销。
从目前看来,最简单的实现方式当属沿用上面的异步回调方式,在里面,每读到 PC 传来的一帧,就将其保存下来;随后在主线程的 Main thread 中,在 Update 中,读取保存的那一帧并进行相应的修改。
如果上面这种方法没有线程安全问题(我现在仍在怀疑)那可再好不过了。但是如果出了问题,我们仍可以中规中矩地新加一个服务器线程,并通过锁的方式与主线程安全地共享数据。
对于一个懒人来讲, Unity 认为安全我们就认为安全,因此姑且先按照异步的方式实现试一试。
记录一下这些可能有用的回答:
LeapMotion Rendering
从 head mounted 视角来看,$x$ 轴正方向为左,$y$ 轴正方向为前,$z$ 轴正方向为下。同时,其单位为毫米 (mm) 。