转自微信:Unity官方平台 、 cocoachina
这篇文章翻译自外国文章。
如果你已经熟悉编程的概念,理解并在C#语言方面有一定的经验,并且对面向对象编程思想和设计概念有所熟悉。如果你了解3D图像学和向量数学知识。不妨来看看吧~本文使用Unity 5.1~文中涉及的所有代码也能在旧版引擎上编译通过。
Leap Motion是什么?
在一只手上有29根骨头,29个关节,123根韧带,48根神经,30根动脉,复杂吧!还得乘以2,Leap Motion已经能够非常逼真的进行模拟了。Leap Motion感知你移动的双手,让你以一种新的方式与电脑交互,它以1毫米的100分之1的精度追踪你的10根手指,这大大超过现有的运动控制技术的敏感度,这就是为什么你可以在1英寸的立方体空间内画出微型杰作的原因。
Leap Motion设置和Unity 集成
首先,你需要下载Leap Motion SDK,大家可以到developer.leapmotion.com 网站下载最新的版本,本文使用的是DK v.2.2.6.29154,下载完后,继续安装运行时,这个SDK支持所有的平台和语言,鼓励大家去了解下这些平台和语言。为了达成集成到Unity 中的目标,还需要下载Leap Motion Unity Core Assets v2.3.1。
假定你已经下载安装了Unity 5,下一步就是从Asset Store中获得Leap Motion Unity Core Assets 这个包,本文中用的是2.3.1版本。
图2.Leap Motion Core Assets
下载安装完这个包后,我们就可以开始创建一个简单的场景,并且开始学习如何使用这些资源,并且将它们扩展到适合我们的要求。
图3.Leap Motion在Project视图中的结构
继续创建一个空的Unity 工程,并且导入Leap Motion Core Assets包,当导入完后,项目的目录结构为上图所示。
Leap Motion Core Assets
在Project窗口里,你最该注意的是Leap Motion文件夹,这个文件夹包含的OVR是Leap Motion与Oculus Rift 虚拟现实头戴式设备相结合的资源,这个在以后会涉及到。你应该花时间学习每个文件夹内的结构,更重要的是内容。其中最主要的一个核心资源是Hand Controller,这个是允许你和Leap Motion设备交互的主要预制体,它作为锚点将你的双手渲染到场景中。
图4.Hand Controller属性面板
Hand Controller 预制体有一个Hand Controller 脚本附加在上面,这样就允许了你和设备的交互。看一看检视面板里面的一些属性,你会发现那里有两个关于手的属性合作来在场景中渲染出真正的手部模型,并且那里有两个物理模型,这些是碰撞,这样设计的好处是你能创建你自己的手部模型,并且用在控制器里来查看效果,并且可以自定义手势等等。
注意:Unity 和Leap Motion 都是使用的米制系统,但是有些区别:Unity 的是以米为单位,Leap Motion用的是毫米,不是什么大问题,但是当你测量坐标的时候需要知道。
另一个关键属性是Hand Movement Scale 向量,缩放值越大,设备覆盖的物理世界范围越大,你需要查看文档来找到一个合适的数值来适应你当前的应用。Hand Movement Scale向量是用来在不改变模型大小的前提下改变双手移动的范围。
放置Hand Controller物体在场景中是重要的。如上所述,这是锚点的位置,因此,摄像机应该和Hand Controller在同一区域,在我们的示例场景中,将Hand Controller放置在(0,-3,3)处,确保摄像机在(0,0,-3)处,看一下坐标下并且看一下组件是如何在3D空间中呈现的,下面是图示:
图5.可以看到Camera及Hand Controller位置
换句话说,你需要将Hand Controller放在摄像机前面,并且相对于摄像机往下一定数值范围内,这里没有魔法数值,你只需尝试适合你的数值即可。
到此,你已经将所有基础的部件结合起来了,并且场景可以运行测试Leap Motion的效果了。
去试试吧,如果你正确的安装了所有的软件组件,你将会在场景中看到你的双手。
下一步
你能在场景中看到双手的运动的事实是:通过提供的资源执行大量的任务,但是在现实中,想让事情变得更有趣,你将需要能够和环境交互,改变或调整场景中的东西。
为了说明这具体的一点,我们创建几个GameObject,我们将了解如何与GameObject实现一些基本的交互,我们将让一个立方体悬浮在空气中,我们想通过选择其他Cubes的颜色来改变这个Cube的颜色。
为了场景的简单起见,我们会放置3个立方体来代表取色盘,第一个是红色的,第二个是蓝色的,第三个是橘黄色的。你可以选择任何颜色,我们需要将立方体放置的有点规律,不然会让用户感到困惑。
将四个立方体这样放:
- 没颜色的(0,1,3)
- 红的: (3,-1,3)
- 蓝的: (0,-1,3)
- 橘黄色的: (-3,-1,3)
注意它们都在Hand Controller游戏物体Y轴的上面。但是在z轴它们在一个水平线上,你可以调整这些数字如果你喜欢的话,只要这些立方体在Hand Controller的检测范围之内就好。
下一步就是创建一个帮助我们与立方体交互的脚本,起名为scriptCubeInteraction.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using UnityEngine; using System.Collections; public class CubeInteraction : MonoBehaviour { public Color c; public static Color selectedColor; public bool selectable = false; void OnTriggerEnter(Collider c) { if (c.gameObject.transform.parent.name.Equals("index")) { if (this.selectable) { CubeInteraction.selectedColor = this.c; this.transform.Rotate(Vector3.up, 33); return; } transform.gameObject.GetComponent().material.color = CubeInteraction.selectedColor; } } } |
正如你所看到的,这些代码不是很复杂,但是他的确帮助我们实现了我们想要做到的,这三个变量分别表示物体的颜色,物体所选择的颜色,物体是否为可以选择。
逻辑的核心在OnTriggerEnter(Colliderc) 函数内,我们检查与可选择物体的碰撞对象是不是食指,如果是,我们设置为该颜色。
另一个好主意是,我们设计这么一种交互,当食指与可选择颜色的立方体产生碰撞的后,让它旋转33度,这样我们能更直观的看出交互效果。
还是用同样的函数,在这个特别的例子里,我们获得立方体的Renderer组件,并设置他的材质颜色为我们所选择的颜色。
运行场景
下面是上述设置的截屏:
图6.使用左手运行Demo
你可以看到我的左手在截屏中,我的右手在用鼠标点击开始截屏。下面是选择蓝色的截图:
图7.选中蓝色立方体
最后将选中的蓝色应用到可变颜色的Cube上:
图8.将蓝色应用到可变颜色的立方体上
移动物体是怎样的呢?
看到这里,你可能会觉得这好酷啊,但是如果还想实现与3D场景更进一步的交互,会不会更让人激动呢?例如说,你想捡起一个物体,并且将它移动到其他地方。如果想让它实现,我们就得写更多的代码。扩展我们的例子,我们将实现一个可以让我们捡起并移动立方体的项目。
抓起一个指定的物体由GrabMyCube.cs实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
using UnityEngine; using UnityEngine.UI; using System.Collections; using Leap; public class GrabMyCube : MonoBehaviour { public GameObject cubePrefab; public HandController hc; private HandModel hm; public Text lblNoDeviceDetected; public Text lblLeftHandPosition; public Text lblLeftHandRotation; public Text lblRightHandPosition; public Text lblRightHandRotation; // Use this for initialization void Start() { hc.GetLeapController().EnableGesture(Gesture.GestureType.TYPECIRCLE); hc.GetLeapController().EnableGesture(Gesture.GestureType.TYPESWIPE); hc.GetLeapController().EnableGesture(Gesture.GestureType.TYPE_SCREEN_TAP); } private GameObject cube = null; // Update is called once per frame Frame currentFrame; Frame lastFrame = null; Frame thisFrame = null; long difference = 0; void Update() { this.currentFrame = hc.GetFrame(); GestureList gestures = this.currentFrame.Gestures(); foreach (Gesture g in gestures) { Debug.Log(g.Type); if (g.Type == Gesture.GestureType.TYPECIRCLE) { // create the cube ... if (this.cube == null) { this.cube = GameObject.Instantiate(this.cubePrefab, this.cubePrefab.transform.position, this.cubePrefab.transform.rotation) as GameObject; } } if (g.Type == Gesture.GestureType.TYPESWIPE) { if (this.cube != null) { Destroy(this.cube); this.cube = null; } } } foreach (var h in hc.GetFrame().Hands) { if (h.IsRight) { this.lblRightHandPosition.text = string.Format("Right Hand Position: {0}", h.PalmPosition.ToUnity()); this.lblRightHandRotation.text = string.Format("Right Hand Rotation: ", h.Direction.Pitch, h.Direction.Yaw, h.Direction.Roll); if (this.cube != null) this.cube.transform.rotation = Quaternion.EulerRotation(h.Direction.Pitch, h.Direction.Yaw, h.Direction.Roll); foreach (var f in h.Fingers) { if (f.Type() == Finger.FingerType.TYPE_INDEX) { // this code converts the tip position from leap motion to unity world position Leap.Vector position = f.TipPosition; Vector3 unityPosition = position.ToUnityScaled(false); Vector3 worldPosition = hc.transform.TransformPoint(unityPosition); //string msg = string.Format("Finger ID:{0} Finger Type: {1} Tip Position: {2}", f.Id, f.Type(), worldPosition); //Debug.Log(msg); } } } if (h.IsLeft) { this.lblLeftHandPosition.text = string.Format("Left Hand Position: {0}", h.PalmPosition.ToUnity()); this.lblLeftHandRotation.text = string.Format("Left Hand Rotation: ", h.Direction.Pitch, h.Direction.Yaw, h.Direction.Roll); if (this.cube != null) this.cube.transform.rotation = Quaternion.EulerRotation(h.Direction.Pitch, h.Direction.Yaw, h.Direction.Roll); } } } } |
这个脚本里实现好几个功能,我将指出最重要的部分,剩下的大家自己摸索,例如,我不会涉及讲解UI界面方面的代码等等。你可以看看别人写的一些关于新的UGUI的系列博文。
我们定义了一个Hand Controller对象hc,这里就引用了Hand Controller对象,我们就可以在此脚本中调用此对象中的功能,第一件事是,我们需要用HandController对象注册手势,这在Start()方法中完成,这里有一些预先定义好的手势,所以我们将使用其中的一些,在这个示例中,我们注册了食指转圈,扫动,屏幕轻触手势类型。
我们还定义了两个GameObject变量,名为cubePrefab和cube,cubePrefab是我们引用的一个代表我们的立方体的预制体,这个预制体有对应材质和其他一些组件附加其上。
让我们看看Update()函数,这是所有魔法发生的地方,这也许会有点疑惑,但是你马上会了解它,我们在这里查找手势类型为typecircle,这将实例出一个我们预先制作好的名为cubePrefab的预制体,所以,第一件事是将当前帧对象传递到Hand Controller对象,一个帧对象包含了所有有关手部运动的信息,下一步是获得由传感器检测到的所有手势并储存为一个列表。
下一步我们将循环遍历每一个手势,并检测它的类型,如果我们检测到CIRCLE手势,我们就检查,我们是否已经实例化了我们的立方预制体,如果没有,实例化它,如何下一个手势类型是SWIPE,这将销毁我们实例化的预制体。
下一个循环基本上遍历检测完所有的手,检测它是左手还是右手,基于哪只手在执行特定的操作,在这个例子里,我们只是获得手的位置和旋转,并且根据手部的旋转来旋转我们实例化的立方体,没什么奇怪的。