Leap Motion 學習筆記

如果你已經熟悉編程的概念,理解並在C#語言方面有一定的經驗,並且對面向對象編程思想和設計概念有所熟悉。如果你瞭解3D圖像學和向量數學知識。不妨來看看吧~本文使用Unity 5.1~文中涉及的所有代碼也能在舊版引擎上編譯通過。

Leap Motion是什麼?

在一隻手上有29根骨頭,29個關節,123根韌帶,48根神經,30根動脈,複雜吧!還得乘以2,Leap Motion已經能夠非常逼真的進行模擬了。Leap Motion感知你移動的雙手,讓你以一種新的方式與電腦交互,它以1毫米的100分之1的精度追蹤你的10根手指,這大大超過現有的運動控制技術的敏感度,這就是爲什麼你可以在1英寸的立方體空間內畫出微型傑作的原因。

QQ截圖20150716113814.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版本。

QQ截圖20150716113928.jpg

圖2.Leap Motion Core Assets

下載安裝完這個包後,我們就可以開始創建一個簡單的場景,並且開始學習如何使用這些資源,並且將它們擴展到適合我們的要求。

QQ截圖20150716114030.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設備交互的主要預製體,它作爲錨點將你的雙手渲染到場景中。

QQ截圖20150716114128.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空間中呈現的,下面是圖示:

QQ截圖20150716114213.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組件,並設置他的材質顏色爲我們所選擇的顏色。

運行場景

下面是上述設置的截屏:

QQ截圖20150716114330.jpg

圖6.使用左手運行Demo

你可以看到我的左手在截屏中,我的右手在用鼠標點擊開始截屏。下面是選擇藍色的截圖:

QQ截圖20150716114412.jpg

圖7.選中藍色立方體

最後將選中的藍色應用到可變顏色的Cube上:

QQ截圖20150716114452.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 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 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,這將銷燬我們實例化的預製體。

下一個循環基本上遍歷檢測完所有的手,檢測它是左手還是右手,基於哪隻手在執行特定的操作,在這個例子裏,我們只是獲得手的位置和旋轉,並且根據手部的旋轉來旋轉我們實例化的立方體,沒什麼奇怪的。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章