2012年9月19日 星期三

Some extensions for AIRKinect(3)KinectCursor與KinectButton

接著我們來解決一個問題:在電腦上,我們用滑鼠點選按鈕;在行動裝置觸控螢幕上,我們用手指點選按鈕;在Kinect上,我們也會面臨提供一個或多個按鈕,讓使用者來選取。根據Human Interface Guidelines,我們可以設計一個游標跟著user的左手或右手移動,而當游標碰到按鈕時,如果直接觸發選取的效果,很容易發生user誤觸的狀況。解決的方式有兩種:一種是當user手往前推,才會試著去判斷是否選取某個按鈕;第二個方法是,當游標移動到按鈕上,進入hover狀態,按鈕出現一些hover的視覺效果(例如變色),而游標進入一個倒數的狀態,在倒數完成前,uer可以把游標移開,此時倒數就會停止,按鈕與游標都回到normal狀態。若游標倒數時間到了,則進入selected狀態,程式就去處理按鈕被選取後該做的事。我們選擇第二種方法來實作,一個游標與按鈕的示意圖如下:

要完成這樣的工作,我們可以想見有幾個功能要完成: 1. 要有個系統不斷去檢查所有游標與按鈕是否碰撞。
2. 游標要有一般normal與倒數狀態,要有個屬性可以決定倒數的時間長短。
3. 按鈕要有normal、hover與selected狀態。

這幾件事情不難,不過每次都要重寫這些功能的話會很累人,所以我們把基本功能寫進extensions裡面,要用的時候再透過繼承,然後去覆寫主要的方法,就可以節省開發的時間。首先我們建立一個CursorManager類別,它負責管理所有的游標與按鈕的清單,並且會不停的去檢查游標與按鈕的碰撞情形。它被設計成一個Singleton,並且透過呼叫static public方法來做事(就像Facebook AS3 API裡的Facebook類別用法一樣)。不過目前它會自動被按鈕與游標呼叫,並且默默的完成它的工作,所以開發者大概也感覺不到它的存在。

接著我們建立KinectCursor類別,繼承自MovieClip,當這個類別的實體加到display list時它自動會要求加入CursorManager的游標列表。當我們要開始讓user控制這個游標時,呼叫它的startTracking(user, range)方法,並傳入要追蹤的User(User類別)以及一個範圍(Rectangle類別),這個範圍是我們希望可以傳入一個user容易操作的範圍,也就是右手容易觸及到的範圍,將它對應到整個視窗上。示意如下圖:

黑色外框是Kinect截取到的影像,紅色框表示右手最容易活動的範圍,我們需要傳入的就是紅色框框的左上角座標以及寬高,為了在每種影像大小都可以使用,我們使用Relative座標,也就是0~1的值。要停止追蹤游標的話呼叫stopTracking()來停止。

在KinectCursor的子類別中,我們透過holdingTime這個屬性來設定要倒數多少時間,單位是毫秒。覆寫updatePercent(percent)來更新倒數的進度,percent為0~1的值。另外覆寫setNormal()來回到normal狀態。

按鈕的部分我們建立KinectButton類別,它在加入或移出display list時會自動加入或移出CursorManager的按鈕清單。在被KinectCursor選取後會送出CursorEvent.SELECTED事件,這是我們自定的的事件。要在按鈕的三種狀態處發時去處理視覺變化,可以在子類別裡覆寫atHover()、atNormal()與atSelected()方法,或是在主程式指定實體的onHover、onNormal與onSelected方法(類似Progression的設計方式)。

CursorManager、KinectCursor、KinectButton是在com.as3nui.nativeExtensions.air.kinect.extensions.display這個package裡,而CursorEvent則是在com.as3nui.nativeExtensions.air.kinect.extensions.events這個package。下面來看一下實際專案時要寫哪些程式碼。myCursor是游標實體,MyCursor類別繼承KinectCursor。btn是按鈕實體,BtnSquare繼承KinectButton。MyCursor類別需要做以下的事情:
public function MyCursor() 
{
  init();
}

private function init():void 
{
  //設定倒數時間
  holdingTime = 500;
  stop();
}

override public function updatePercent(percent:Number):void 
{
  //覆寫updatePercent,這邊進度已作成影格動畫,所以跑影格就好
  gotoAndStop(1 + int(percent * 100));
}

override public function setNormal():void 
{
  //覆寫setNormal,回到第1個影格恢復normal狀態
  gotoAndStop(1);
}

接下來BtnSquare的程式碼:
//只是一個顏色方塊
public var square:Sprite;

public function BtnSquare() 
{
  init();
}

private function init():void 
{
  stop();
}

override public function atHover():void 
{
  //覆寫atHover,改變方塊顏色
  TweenMax.to(square, 0.5, {tint:0x990000});
}

override public function atNormal():void 
{
  //覆寫atNormal,改變方塊顏色
  TweenMax.to(square, 0.5, {tint:null});
}

override public function atSelected():void 
{
  //覆寫atSelected,改變方塊顏色
  TweenMax.to(square, 0, {tint:0xff0000});
}

最後,主程式跟游標與按鈕相關的程式碼:
private function init():void
{
  //按鈕加上監聽CursorEvent.SELECTED事件,然後開始追蹤游標
  btn.addEventListener(CursorEvent.SELECTED, btnSelected);
  startTrackingCursor();
}
private function startTrackingCursor():void
{
  //追蹤游標,_user是Kinect追蹤到的User
  myCursor.visible = true;
  myCursor.startTracking(_user, new Rectangle(0.5, 0.1, 0.25, 0.4));
}

private function stopTrackingCursor():void
{
  //停止追蹤
  myCursor.visible = false;
  myCursor.stopTracking();
}
  
private function btnSelected(e:CursorEvent):void 
{
  //按鈕選到後做該做的事
  removeChild(btn);
  stopTrackingCursor();
  removeChild(myCursor);
  //做其他事...
}

就是這樣!我想應該已經把工作精簡到不能再精簡了。

最後要回頭提一個問題:如果我們直接把user右手對應的座標指定給游標,可以想見游標一定抖動非常厲害!要解決這個問題,參考Kinect Toolbox,要做一些運算去過濾掉抖動的狀態,當然這會使游標反應稍微慢一點,但抖動的情形改善很大。Kinect Toolbox是C#程式,我已經將相關的運算改寫成AS3並整合到CursorManager裡,有興趣的請自行翻出來研究。

程式碼與範例,下一篇再提供連結!

上一篇:Some extensions for AIRKinect(2)KinctSprite
下一篇:Some extensions for AIRKinect(4)KinectGestureRecognizer

6 則留言:

  1. 嗨!Gray大!我是阿邪!

    我目前有一個也是關於Kinect滑鼠點擊的問題

    不過點擊的對象不是Sprite也不是MovieClip

    而是AIR的HTMLLoader載進來的HTML內容!

    一般我們Sprite與MovieClip使用Kinect滑鼠可以dispatch一個MouseEvent去模擬點擊到的滑鼠事件

    不過在載入的HTML內容裡,要去點內容裡的連結反而不知該用什麼方法!不知你有沒有在這方面經驗與解決的辦法??

    回覆刪除
    回覆
    1. Hello 阿邪:
      你好
      我懂你的意思
      因為我們沒辦法知道Load進來的HTML是甚麼東西
      確實是不知道要怎麼判斷我們的滑鼠游標有沒有點到HTML上的button或link
      AS3應該也沒有能力去移動真正的滑鼠

      反過來想,用Kinect來瀏覽一般網頁好像沒甚麼道理
      所以我猜你們載入的HTML內容是你們自己做的特殊內容嗎?
      是的話或許可以把感應區位置等等的資訊偷塞給AIR
      不是的話...我還真想不出其他解法了

      刪除
    2. 其實我是要做一個多功能系統!

      其中一個功能就是用Kinect滑鼠來瀏覽一般網頁!

      看來要先放掉網頁瀏覽這一個項目了= =

      刪除
  2. 其實我有想到一個方法!但是不知可不可行!

    既然FLASH不能控制滑鼠點擊!交由C#來點擊應該就沒問題(看到C#只要輸入座標就可以模擬滑鼠還有點擊)!

    所以就變成要寫一個中介程式來聯絡FLASH與C#,需要的時候就傳送座標呼叫C#滑鼠左鍵的函式!目前的想法是這樣!不過沒寫過C#,要來試一下!

    回覆刪除
  3. Gray, 你好! 我是個airkinect的新手, 我想問一下第一種方法, 用手向前推來按button, 我該怎麼做,因為我沒有什麼概念去做,可以給我一點方法嗎?

    回覆刪除
    回覆
    1. 先偵測是否碰到button,再偵測手的z坐標改變量

      刪除