在cocos2d-x裏面手勢識別

在本教程中,即將帶來的是在cocos2d-x裏面使用手勢識別,然後寫一個帶有手勢識別的遊戲。該遊戲的功能是,當手勢是↑,↓的時候,忍者就跳躍,當手勢是↓,↓,↑的時候,忍者發射一顆子彈,當手勢是↑,↓,↓的時候,忍者發射一個飛鏢。呵呵,什麼亂七八糟的動作,不過,手勢識別併入遊戲還是很不錯的感覺。
  如果你對cocos2d-x編程不瞭解,可以到我博客上找相關的文章。本教程假定你已經學過前面的《用cocos2d-x做一個簡單的windows phone 7遊戲》系列教程,或者擁有同等的經驗。
  現在新建一個工程,命名爲cocos2dGestureDemo,和前面的教程一樣,openXLive不需要,並且也添加了需要的Lib和修復之。下載:http://dl.dbank.com/c0c4orbdka,並且添加所以圖片到Image文件夾。


  現在先添加一個類GestureScene到Classes。並且讓之繼承於CCScene。修改代碼如下:


   class GestureScene:CCScene
    {
        public GestureScene()
        {
            this.addChild(GestureLayer.node());
        }
    }


    class GestureLayer : CCLayer
    {
        public override bool init()
        {
            if (!base.init())
                return false;
            return true;
        }


        public static new GestureLayer node()
        {
            GestureLayer layer = new GestureLayer();
            if (layer.init())
                return layer;
            return null;
        }
    }

並且修改AppDelegate的Launch方法。

            //CCScene pScene = cocos2dGestureDemoScene.scene();
            GestureScene pScene = new GestureScene();
            //run
            pDirector.runWithScene(pScene);

現在,基本工作就做好了。

  現在來說下手勢識別的問題吧。在cocos2d-x,沒有封裝關於手勢識別的東西,沒辦法,我們只有用基礎的XNA的手勢識別了。關於XNA的手勢識別,我也不想多講,直接看臺灣的MSDN的文章吧。這裏講得不錯。不過是繁體字,應該都能看懂的吧。http://msdn.microsoft.com/zh-tw/windowsphone/gg490792.aspx

那麼,現在來添加手勢識別吧。在GestureLayer類中添加一個聲明:
        
CCLabelTTF title;

接着在init方法添加代碼:

            title = CCLabelTTF.labelWithString("gesture", "Arial", 32);
            title.position = new CCPoint(400, 400);
            this.addChild(title);
            TouchPanel.EnabledGestures = GestureType.Hold | GestureType.Tap | GestureType.DoubleTap 
                | GestureType.FreeDrag | GestureType.Flick
             | GestureType.Pinch | GestureType.HorizontalDrag | GestureType.VerticalDrag;
            this.schedule(gestureRecognize);

添加一個方法:
   
     void gestureRecognize(float dt)
        {
            while (TouchPanel.IsGestureAvailable)
            {
                GestureSample gesture = TouchPanel.ReadGesture();
                switch (gesture.GestureType)
                {
                    case GestureType.DoubleTap:
                        {
                            title.setString("DoubleTap");
                            break;
                        }
                    case GestureType.DragComplete:
                        {
                            title.setString("DoubleTap");
                            break;
                        }
                    case GestureType.Flick:
                        {
                            title.setString("DoubleTap");
                            break;
                        }
                    case GestureType.FreeDrag:
                        {
                            title.setString("FreeDrag");
                            break;
                        }
                    case GestureType.Hold:
                        {
                            title.setString("Hold");
                            break;
                        }
                    case GestureType.HorizontalDrag:
                        {
                            title.setString("HorizontalDrag");
                            break;
                        }
                    case GestureType.None:
                        {
                            title.setString("None");
                            break;
                        }
                    case GestureType.Pinch:
                        {
                            title.setString("Pinch");
                            break;
                        }
                    case GestureType.PinchComplete:
                        {
                            title.setString("PinchComplete");
                            break;
                        }
                    case GestureType.Tap:
                        {
                            title.setString("Tap");
                            break;
                        }
                    case GestureType.VerticalDrag:
                        {
                            title.setString("VerticalDrag");
                            break;
                        }
                    default:
                        {
                            title.setString("default");
                            break;
                        }
                }
            }
        }

上面我們做了什麼呢,如果看了上述提到的MSDN的文章應該都懂了,不過也來解釋下吧。在上面,註冊了TouchPanel的手勢識別時間,在週期執行的gestureRecognize裏面,先判斷是否有手勢沒處理,如果有,就進行處理。
編譯運行,你就能看到label的改變了。





具有手勢識別的遊戲

  接下來做點有趣的事情吧。做我們想要的擁有手勢識別功能的遊戲。曾記得,NDS的遊戲就是如此手勢識別,什麼上上下幹什麼動作,下下上做什麼動作,。。。。那麼來實現下吧。

我們所有的操作都在GestureLayer這個層上面。

先添加些聲明:

        Dictionary<string, List<string>> gestureAction;
        List<List<string>> matchGesture;
        float intervalTime = 0;//動作間隔時間
        int gestureTime = 0;//第幾次動作
        float recognizeTime = 0.2f;//識別時間
        CCSprite player;

上面定義了些變量,我們怎麼做手勢識別呢,Dictionary定義了所有動作的集合,matchGesture來保存匹配的手勢。intervalTime來判斷上一動作和下一動作間隔的時間,如果時間超過0.8秒。那麼就執行系列動作需要產生的效果。recognizeTime是一個識別動作間隔時間的常量,這個使動作間隔穩定些,使動作更容易判斷。

 因爲我們只需要豎直方向的移動的手勢識別。那麼添加並且修改init方法:

            initGestureLib();
            TouchPanel.EnabledGestures = GestureType.VerticalDrag;
            player = CCSprite.spriteWithFile(@"images/Player");
            player.position = new CCPoint(100, 200);
            this.addChild(player);
            this.schedule(gestureRecognize);

上面我們添加了一個player,並且修改手勢註冊只有豎直方向的移動。

修改gestureRecognize方法並且添加一個方法:

        void gestureRecognize(float dt)
        {
           while (TouchPanel.IsGestureAvailable)
            {
                GestureSample gesture = TouchPanel.ReadGesture();
                switch (gesture.GestureType)
                {
                    case GestureType.VerticalDrag:
                        {
                            break;
                        }
                    default:
                        break;
                }
            }
        }


void initGestureLib()
        {
            gestureAction = new Dictionary<string, List<string>>(); ;
            matchGesture = new List<List<string>>();
            gestureAction["projectile"] = new List<string>() { "↑", "↓", "↓" };
            gestureAction["jump"] = new List<string>() { "↑", "↓" };
            gestureAction["bullet"] = new List<string>() { "↓", "↓", "↑" };
        }

上面,我們初始化了手勢動作的集合並且修改了gestureRecognize方法只能識別豎直移動。


下面,我們要對手勢來進行識別,要怎麼做呢,首先,應該識別手勢是上還是下的。然後先判斷如果是第一個動作,從gestureAction裏面找匹配的並且添加到matchGesture裏面,不是第一個動作的話,就從matchGesture裏面尋找list中該位置是否和這個動作一致,不一致就從matchGesture中移除。簡單來說,動作的識別就是一遍遍遍歷matchGesture,不一致的移除,剩下的就是和動作匹配的啦。接下來添加一些方法。


 
       /// <summary>
        /// 收集動作並且做動作的篩選
        /// </summary>
        /// <param name="gesture"></param>
        void collectGesture(GestureSample gesture)
        {
            string gestureName;
            if (gesture.Delta.Y > 0)
                gestureName = "↓";
            else
                gestureName = "↑";
            if (gestureTime == 0 && matchGesture.Count != 0)
            {
                matchGesture.Clear();
            }
            Debug.WriteLine(String.Format("gestureName:{0},GestureTime:{1},gesture.y:{2},x:{3}",
                gestureName, gestureTime, gesture.Delta.Y, gesture.Delta.X));
            if (gestureTime == 0)
            {
                foreach (var item in gestureAction.Values)
                {
                    if (item[0].Equals(gestureName))
                    {
                        matchGesture.Add(item);
                    }
                }
            }
            else
            {
                if (matchGesture.Count <= 0)
                {
                    gestureTime = 0;
                    intervalTime = 0;
                    return;
                }
                List<List<string>> toDelete = new List<List<string>>();
                foreach (var item in matchGesture)
                {
                    if (item.Count <= gestureTime)
                    {
                        toDelete.Add(item);
                        continue;
                    }
                    if (!item[gestureTime].Equals(gestureName))
                    {
                        toDelete.Add(item);
                    }
                }
                foreach (var item in toDelete)
                {
                    matchGesture.Remove(item);
                }
            }
            gestureTime++;
            intervalTime = 0;


        }


        /// <summary>
        /// 執行相應的動作
        /// </summary>
        void gestureDoAction()
        {
            if (matchGesture.Count <= 0)
            {
                intervalTime = 0;
                gestureTime = 0;
                return;
            }
            foreach (var item in matchGesture)
            {
                if (item.Count == gestureTime)
                {
                    foreach (var key in gestureAction.Keys)
                    {
                        if (gestureAction[key] == item)
                        {
                            playerDoAction(key);
                            break;
                        }
                    }
                    break;
                }
            }
            gestureTime = 0;
            matchGesture.Clear();
        }


        void playerDoAction(string action)
        {
            if (action == "projectile")
            {
                CCSprite projectile = CCSprite.spriteWithFile(@"images/Projectile");
                projectileFly(projectile);
            }
            else if (action == "jump")
            {
                player.runAction(CCJumpBy.actionWithDuration(0.5f, new CCPoint(0, 0), 40, 3));
            }
            else if (action == "bullet")
            {
                CCSprite bullet = CCSprite.spriteWithFile(@"images/bullet");
                projectileFly(bullet);
            }
        }


        void projectileFly(CCSprite projectile)
        {
            projectile.position = new CCPoint(player.position.x, player.position.y);
            projectile.runAction(CCSequence.actions(CCMoveTo.actionWithDuration(0.7f, new CCPoint(CCDirector.sharedDirector().getWinSize().width, player.position.y)),
                CCCallFuncN.actionWithTarget(this, spriteMoveDone)));
            this.addChild(projectile);
        }




        void spriteMoveDone(object sender)
        {
            CCSprite sprite = sender as CCSprite;
            this.removeChild(sprite, true);
        }



上面的方法做了些什麼呢,如果仔細看,應該能看出,收集動作並且做動作篩選,主要做法就是遍歷和匹配,每次收集動作結束的時候,要把intervalTime置爲0,並且把當前動作個數加1.

當要完成動作效果gestureDoAction的時候,就要先判斷matchGesture裏面的東西了,是否爲空,或者沒有最匹配的(也就是裏面的list的count沒有和gestureTime相同,就沒有最合適的動作序列)。

如果有,就執行動作,先從Dictionary裏面取出相對應的key。然後開始執行相應的動作,下面的方法應該都很熟悉了,就是執行了一些動作。
下面,主要的來了。修改gestureRecognize如下:
 
       void gestureRecognize(float dt)
        {
            intervalTime += dt;
            if (intervalTime >= 0.8f)
            {
                intervalTime = 0;
                gestureDoAction();


            }
            while (TouchPanel.IsGestureAvailable)
            {
                GestureSample gesture = TouchPanel.ReadGesture();
                switch (gesture.GestureType)
                {
                    case GestureType.VerticalDrag:
                        {
                            if (gesture.Delta.Y != 0 && intervalTime > recognizeTime)
                            {
                                collectGesture(gesture);
                            }
                            break;
                        }
                    default:
                        break;
                }
            }
        }

上面做了些什麼呢,先判斷intervalTime,也就是距離上次動作的時間,如果大於0.8秒,那麼就執行相應系列動作的效果,不然,如果gesture的Delta值的Y值不等於0(我經過檢測,發現即使在豎直移動,Y值也可能等於0),並且intervalTime大於一定值,就收集動作,爲什麼呢,因爲我發現,如果一開始就蒐集手勢,手勢就多而且有可能不是需要的,因爲基本第一觸點的效果不好,而且大家都不太在乎。

那麼現在可以來運行下了。在模擬器上,用鼠標代替手指劃,是不是已經達到想要的效果了呢,如果沒有,請觀察Debug窗口,那裏應該輸出了你的手勢動作的相關信息,查看是否是想要的手勢。

現在在模擬器已經能不錯的識別手勢動作了。但是在真機上呢,在我的T8788上面,手勢動作的識別效果不太好,畢竟手和鼠標的動作效果不太一樣。鼠標的效果還不錯主要是我多次嘗試調節intervalTime,recognizeTime數值的效果,我想,要在真機上很好的識別,也要多次嘗試調節這兩個數值,或者增加更好的手勢的獲取和判斷方法。

圖片就不上了,畢竟動作難以以圖片捕捉。

本次示例代碼下載:http://dl.dbank.com/c0yla46khj


 何去何從:
  • 增加更有趣的動作。
  • 增加更復雜的動作
  • 使手勢收集和篩選的算法更強大更快。
  • 。。。。。
 


 


 


 


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