65.9K
CodeProject 正在变化。 阅读更多。
Home

UNITY 3D – Leap Motion 集成

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2015 年 7 月 6 日

CPOL

14分钟阅读

viewsIcon

67896

本文将介绍 Leap Motion 集成到 Unity 3D 项目的基础知识。我们将探讨基础设置和基本实现的集成,以帮助您入门。为了能够尝试代码,您需要拥有 Leap Motion 硬件。

引言

人机交互 (HCI) 的未来即将迎来范式转变。自计算开始以来,科学家和工程师们一直致力于创造 3D 环境以及虚拟和增强现实世界,我们在其中整合了数字和物理生活领域。从早期的科幻书籍和电影中,您可以看到我们一直梦想着创造这样的环境和交互。

今天,我们生活在激动人心的时代。我们正逐步将这些科幻想法变为科学事实。事实上,在某种程度上我们已经做到了。虚拟现实 (VR) 是一个庞大的主题。VR 的重要主题之一是与数字世界交互的能力。本文将重点介绍用户使用 Leap Motion 开发的硬件传感器与数字世界的交互。

本文将介绍如何将 Leap Motion 手部动作传感器集成到 Unity 3D 中并进行使用。本文将不涵盖 Unity 3D 的基础知识。请参考以下文章系列,以开始学习 Unity 3D。

如果这是您第一次阅读 Unity 3D 系列文章,我将在此列出该系列的链接:

  1. Unity 3D – 游戏编程 – 第 1 部分
  2. Unity 3D – 游戏编程 – 第 2 部分
  3. Unity 3D – 游戏编程 – 第 3 部分
  4. Unity 3D – 游戏编程 – 第 4 部分
  5. Unity 3D – 游戏编程 – 第 5 部分
  6. Unity 3D – 游戏编程 – 第 6 部分
  7. Unity 3D – 游戏编程 – 第 7 部分
  8. Unity 3D – 游戏编程 – 第 8 部分
  9. Unity 3D – 游戏编程 – 第 9 部分
  10. Unity 3D – 游戏编程 – 第 10 部分

Unity 3D 网络文章

  1. Unity 3D - 网络游戏编程

上面列出的文章将为您提供扎实的 Unity 3D 入门基础。

注意: 为了能够尝试本文提供的代码,您将需要 Leap Motion 硬件。

游戏编程入门:使用 C# 和 Unity 3D (平装本)(电子书) 的设计和开发旨在帮助对计算机科学和游戏编程领域感兴趣的个人。它旨在阐述计算机编程的概念和基础知识。它使用简单游戏的开发和设计来阐述和应用概念。
平装本
ISBN:9780997148404
版本:第一版
出版商:Noorcon Inc.
语言:英语
页数:274
装订:完美装订平装(全彩)
尺寸(英寸):宽 6 x 高 9
Support independent publishing: Buy this book on Lulu.
电子书 (ePUB)
ISBN:9780997148428
版本:第一版
出版商:Noorcon Inc.
语言:英语
大小:9.98 MB
Support independent publishing: Buy this e-book on Lulu.
可从以下网址获取

背景

假定本文的读者普遍熟悉编程概念。还假定读者对 C# 语言有理解和经验。还建议本文读者熟悉面向对象编程和设计概念。我们将在文章中根据需要简要介绍它们,但不会深入细节,因为它们是完全独立的主题。我们还假定您热衷于学习 3D 编程,并具备 3D 图形和向量数学的基本理论概念。

最后,本文使用 Unity 3D 版本 5.1,这是初始发布日期时的最新公开版本。系列中讨论的大多数主题将与游戏引擎的旧版本兼容。

Using the Code

请遵循说明。易于设置。

什么是 Leap Motion?

“仅一只手,就有 29 块骨头,29 个关节,123 条韧带,48 条神经和 30 条动脉。这是极其复杂而精妙的技术(两只手)。但感觉毫不费力。Leap Motion 控制器已非常接近地破解了这一切。”

Leap Motion 控制器能够感知您自然的双手动作,并让您以全新的方式使用计算机。它能够以最高 1/100 毫米的精度跟踪所有 10 根手指。它比现有的运动控制技术灵敏得多。这就是您可以在一英寸的立方体内绘制或绘制微型杰作的原因。

图 1 - Leap Motion 传感器

Leap Motion 设置与 Unity 3D 集成

首先,您需要下载 Leap Motion 的 SDK。您可以使用以下链接获取最新版本:developer.leapmotion.com。我使用的是 SDK v.2.2.6.29154。下载 SDK 后,请继续安装运行时。SDK 支持其他平台和语言,鼓励您进一步了解。为了方便与 Unity 集成,您还需要从 Asset Store 获取 Leap Motion Unity Core Assets。这是直接链接到 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

下载并安装 Leap Motion Core Assets 后,我们就可以开始创建一个简单的场景,并学习如何与这些资源交互以及为我们自己的目的进行扩展。

图 3 - Leap Motion 的项目文件夹结构

请创建一个新的空项目,并将 Leap Motion Core Assets 导入到您的项目中。导入 Leap Motion Assets 后,您的项目窗口应该与上图类似。

Leap Motion Core Assets

在 Project Window 中,您的主要注意力应该放在 `LeapMotion` 文件夹。包含 OVR 的文件夹是用于将 Leap Motion 与 Oculus Rift 虚拟现实头显配合使用的。我们将在未来的文章中介绍这一点。

您应该花时间研究结构,更重要的是,研究每个文件夹内的内容。一个您需要熟悉的主要核心资源是 `HandController`。这是允许您与场景中的 Leap Motion 设备进行交互的主要 **预制体 (PreFab)**。它位于 `LeapMotion` 文件夹下的 `Prefab` 文件夹中。它充当在场景中渲染您的手的锚点。

图 4 - HandController Inspector 属性

`HandController` 预制体附带 Hand Controller 脚本,该脚本使您能够与设备进行交互。查看一些通过 **Inspector Window** 可见的属性。您会注意到,有两个手部属性用于在场景中渲染实际的手部模型,然后有两个属性用于物理模型,即碰撞器。这种设计的优点在于,您可以创建自己的手部模型,并将其与控制器一起用于视觉表示以及自定义手势等……

注意: Unity 和 Leap Motion 都使用公制系统。但是,存在以下区别:Unity 的单位是米,而 Leap Motion 的单位是毫米。这不算什么大问题,但您在测量坐标时需要注意这一点。

另一个关键属性是 Hand Movement Scale 向量。Scale 值越大,设备在物理世界中覆盖的区域就越大。这里需要注意一点,您需要阅读文档和规范,以找出适合您正在处理的特定应用程序的正确和恰当的调整。

Hand Movement Scale 向量用于在不改变模型视觉大小的情况下改变手的运动范围。

在场景中放置 `HandController` 对象很重要,如前所述,这是锚点所在的位置,因此,您的摄像头应该与 `HandController` 位于同一区域。在我们的演示场景中,请将 `HandController` 设置为以下位置:(0,-3,3),分别对应 (x,y,z) 坐标。确保摄像头位于 (0,0,-3)。查看坐标并可视化组件在 3D 空间中的表示。这是一张供您参考的图:

图 5 - HandController 和 Camera 位置的视觉表示

换句话说,您希望 `HandController` 位于 `Camera GameObject` 的前方,并且低于某个阈值。这里没有魔术数字,您只需要找出最适合您的数字。

此时,您已连接了所有基本组件,可以实际运行场景并尝试 Leap Motion。所以请动手试试,如果您正确安装了所有软件组件,您应该能够在场景中看到您的手。

下一步

您能够在场景中可视化您的手部运动这一事实本身,就是所提供资源的一项巨大成就。然而,在现实中,为了让事情更有趣,您需要能够与环境进行交互,并能够更改和操作场景中的内容。

为了说明这一点,我们将创建几个 `GameObject`,并在场景中,我们将研究如何实现与 `GameObject` 的一些基本交互。想法是让一个立方体悬浮在空中。我们希望通过从代表我们的调色板的另一组立方体中选择来更改 `Cube` 的颜色。

为简化场景,我们将放置三个代表我们调色板的立方体。第一个是红色,第二个是蓝色,第三个是橙色。顺便说一句,您可以选择任何颜色。我们需要以一种用户可以轻松且不混淆的方式与它们进行交互的方式来放置这些立方体。

我们可以按以下顺序放置立方体:

  • 立方体 (0,1,3)
  • 红色立方体:(3,-1,3)
  • 蓝色立方体:(0,-1,3)
  • 橙色立方体:(-3,-1,3)

请注意,它们在 Y 轴上位于 `HandController GameObject` 的上方,但在 Z 轴上位于相同的坐标。如果您愿意,可以修改这些数字以进行调整。只要立方体位于 `HandController` 检测范围内并且在其上方,您就应该没问题。

下一步是创建脚本,该脚本将帮助我们与 `Cube GameObjects` 进行交互。我将该脚本命名为 `CubeInteraction.cs`。

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<Renderer>().material.color = 
                                                  CubeInteraction.selectedColor;
      }
   }
}

如您所见,代码并不复杂,但它确实帮助我们实现了我们的目标。有三个 `public` 变量,用于检测对象是否可选择,对象代表的 `color`,以及选定的 `color`。

逻辑的核心发生在 `OnTriggerEnter(Collider c)` 函数中。我们检查与 `Cube` 发生碰撞的对象是否是食指,然后我们检查对象是否可选择。如果对象可选择,我们将 `selectable color` 设置为预定义的 `color` 代码并退出函数。

在设计虚拟对象之间的此类交互时,为用户提供实际的视觉反馈也是一个好主意。在这种情况下,我让选定的 `color` 立方体每次食指与 `GameObject` 碰撞时旋转 33 度。这是我们已选择 `color` 的一个很好的视觉提示。

此函数也用于将选定的 `color` 应用到可以绘制的 `Cube GameObject`。在此特定情况下,我们从 `Cube` 获取 `Renderer` 对象,并将材质 `color` 设置为选定的 `color`。

运行场景

以下是您刚刚设置的场景的示例屏幕截图:

图 6 - 使用左手运行的演示场景

您可以在上面的屏幕截图中看到我的左手。我的右手正拿着鼠标截图!下一个屏幕截图捕获的是选择蓝色立方体。

图 7 - 选择蓝色彩色立方体

最后,将选定的颜色应用于场景中可着色的立方体。

图 8 - 蓝色应用于可绘制立方体

移动物体怎么样?

此时,您可能会对自己说,所有这些都很酷,但如果我想与我的 3D 环境进行更多交互怎么办?例如,如果您想拾起物体并在场景中移动它们。

嗯,这完全有可能,为了实现这一点,我们需要编写更多代码!扩展我们的示例,我们将实现允许您拾起 `Cube` 对象并在环境中移动它的代码。

在场景中抓取给定对象的列表已在 `GrabMyCube.cs` 中提供。

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: <{0},{1},{2}>", 
                 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: <{0},{1},{2}>", 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 代码等。您可以参考文章系列,了解有关如何设置和使用新 UI 框架的详细说明。

我们有一个 `public HandController` 对象,命名为 `hc`。这是场景中 `HandController` 的一个引用,因此我们可以根据需要访问函数和属性。我们需要做的第一件事是在 `HandController` 对象上注册手势。这在 `Start()` 函数中完成。默认情况下,有一些预定义的手势,因此我们将使用其中的一些。在此情况下,我们注册了 `CIRCLE`、`SWIPE` 和 `SCREEN_TAP` 手势类型。

我们还定义了两个 `GameObject` 类型的变量,分别名为 `cubePrefab` 和 `cube`。变量 `cubePrefab` 是一个预制体的引用,该预制体被创建以表示具有相关材料和组件的我们的立方体。

让我们看一下 `Update()` 函数。这是所有内容发生的核心,一开始可能会有点令人困惑,但您很快就会掌握它。我们在这里做的是寻找一个 `TYPECIRCLE` 手势。这将实例化我们定义的并且在 `cubePrefab` 变量中引用的预制体。因此,我们在函数中所做的第一件事是从 `HandController` 对象中获取当前帧。`Frame` 对象包含有关传感器在该给定实例处的手部运动的所有信息。下一步是获取传感器检测到的所有手势的列表,并将其存储在一个列表中。

接下来,我们将循环遍历每个手势并检测类型。如果我们检测到 `CIRCLE` 手势,我们将检查是否已实例化我们的立方体预制体,如果没有,我们将继续实例化它。下一个手势类型是 `SWIPE`,这将销毁我们实例化的预制体。

下一个循环基本上遍历当前帧中检测到的手,并检测它是左手还是右手,然后根据它是哪只手执行特定操作。在这种情况下,我们只是获取手的位置和旋转,并根据右手或左手的旋转来旋转我们实例化的立方体。没什么花哨的!

您可以在以下视频中看到结果:演示

关注点

虚拟现实一直是行业中一个话题,它会在某个时期获得大量关注,然后一切都会冷却下来。这一次,情况有所不同。使虚拟现实成为可能的硬件和软件生态系统正变得越来越民主化。硬件成本,虽然不便宜,但对于那些真正渴望亲自动手的人来说是经济实惠的,以至于大多数开发人员都能负担得起。

这使得开发社区能够提供更好的 VR 体验和娱乐。将手部动作和手势与 VR 应用程序集成是必须的。我决定研究 Leap Motion,因为它是一种有前途的输入传感器,可用于非触摸式用户与计算机的交互。下一步将是集成 Leap Motion 和 Oculus Rift。

Unity 3D 文章系列

  1. Unity 3D – 游戏编程 – 第 1 部分
  2. Unity 3D – 游戏编程 – 第 2 部分
  3. Unity 3D – 游戏编程 – 第 3 部分
  4. Unity 3D – 游戏编程 – 第 4 部分
  5. Unity 3D – 游戏编程 – 第 5 部分
  6. Unity 3D – 游戏编程 – 第 6 部分
  7. Unity 3D – 游戏编程 – 第 7 部分
  8. Unity 3D – 游戏编程 – 第 8 部分
  9. Unity 3D – 游戏编程 – 第 9 部分
  10. Unity 3D – 游戏编程 – 第 10 部分

Unity 3D 网络文章

  1. Unity 3D - 网络游戏编程
© . All rights reserved.