开发实例:Unity集成Leap Motion

1437017849129276.jpg

如果你已经熟悉编程的概念,理解并在C#语言方面有一定的经验,并且对面向对象编程思想和设计概念有所熟悉。如果你了解3D图像学和向量数学知识。不妨来看看吧~本文使用Unity 5.1~文中涉及的所有代码也能在旧版引擎上编译通过。
Leap Motion是什么?
在一只手上有29根骨头,29个关节,123根韧带,48根神经,30根动脉,复杂吧!还得乘以2,Leap Motion已经能够非常逼真的进行模拟了。Leap Motion感知你移动的双手,让你以一种新的方式与电脑交互,它以1毫米的100分之1的精度追踪你的10根手指,这大大超过现有的运动控制技术的敏感度,这就是为什么你可以在1英寸的立方体空间内画出微型杰作的原因。
1437017932352953.jpg

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版本。
1437018008171993.jpg

图2.Leap Motion Core Assets
下载安装完这个包后,我们就可以开始创建一个简单的场景,并且开始学习如何使用这些资源,并且将它们扩展到适合我们的要求。
1437018066323759.png

图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设备交互的主要预制体,它作为锚点将你的双手渲染到场景中。
1437018115522164.jpg

图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空间中呈现的,下面是图示:
1437018154959911.jpg

图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组件,并设置他的材质颜色为我们所选择的颜色。
运行场景
下面是上述设置的截屏:

1437018235749470.jpg

图6.使用左手运行Demo
你可以看到我的左手在截屏中,我的右手在用鼠标点击开始截屏。下面是选择蓝色的截图:
[图片上传中。。。(8)]
图7.选中蓝色立方体
最后将选中的蓝色应用到可变颜色的Cube上:
1437018311342695.jpg

图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,这将销毁我们实例化的预制体。
下一个循环基本上遍历检测完所有的手,检测它是左手还是右手,基于哪只手在执行特定的操作,在这个例子里,我们只是获得手的位置和旋转,并且根据手部的旋转来旋转我们实例化的立方体,没什么奇怪的。
Demo演示视频

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 本节将指导您从头开始创建将在本教程中使用的Player Prefab,以便我们涵盖创建过程的每一步。 这里介绍一个...
    浪尖儿阅读 1,609评论 0 2
  • 私己在理 又不顾源起 只其然息 却无多能力 怕到头来唏嘘不已 现要苦撑避于言嬉 直此 之至 念回转 难矣
    卅森阅读 173评论 0 0
  • 想了你一下 偷笑了大半天 失眠了一整晚 听一句歌词 单曲循环了半小时 失眠了一整晚 打了一声雷 听了一夜雨 失眠了...
    下寒阅读 194评论 0 0
  • 生活,我更喜欢踏实,精彩却也简单。
    小鬼生活阅读 172评论 0 0
  • 我一直以为居于小城市的我,缺少大城市的空间,让自己得不到舒展,于是,不断抱怨,不断找理由,不断说服自己相信这就是原...
    灿烂若你阅读 368评论 0 0