网上搜索cocos2d-x方面的手势识别资料相对较少,即使有也是着重讲的单击以及双击的的实现方法。
而记得有一篇提及了在cocos2d下手势识别的一些工具类。其中提到了$1Unistroke Recognizer手势识别
官方网站:http://depts.washington.edu/aimgroup/proj/dollar/
参考资料:http://blog.sina.com.cn/s/blog_61ece09901017ngf.html
教程代码下载链接:http://download.csdn.net/detail/mr_dodododo/6225657
压缩包中存在文件:
官方:
GeometricRecognizer.cpp(主要手势判断逻辑类)
GeometricRecognizer.h
GeometricRecognizerTypes.h(自定义对象)
GestureTemplate.h(模板存储)
SampleGestures.h(模板)
使用:
CTouchManager.cpp
CTouchManager.h
直接开门见山的讲讲 官方提供的C++代码的实现原理:
由于官方提供代码中方法较多,我就不根据代码来一一说明,只介绍大概的方法流程,方便大家理解,具体实现,可以自行学习。
使用之前:
需要使用者自己配置形状模板的点集
初始化:
1.加载模板的各个形状
2.将各形状点集“标准化”。(若使用者不自行更改,会将其处理为128个点组成的平滑形状。可在GeometricRecognizer构造函数中更改其值)
使用判断形状:
1.获得用户所画形状的点集
2.将点集“标准化”(若使用者不自行更改,会将其处理为128个点组成的平滑形状)
3.将“标准化”后的点集与初始化时加载的形状模板进行对比。最终得到一个最佳结果返回(存在一个角度误差值,值越大形状包容性越大,值越小形状越具有唯一性,根据具体需求自行改变。主要是在setRotationInvariance(bool ignoreRotation)函数中设置angleRange的值)
如何使用:
主要参见CTouchManager类中的使用方法。
CTouchManager.cpp
CTouchManager* CTouchManager::create( CCObject* pTarget, Touch_ClickCallBack callbackClick, Touch_GestureCallBack callbackGesture ) { CTouchManager* mTouchManager = new CTouchManager(); if( mTouchManager && mTouchManager->init( pTarget, callbackClick, callbackGesture ) ){ return mTouchManager; } else{ delete mTouchManager; return NULL; } return NULL; } bool CTouchManager::init( CCObject* pTarget, Touch_ClickCallBack callbackClick, Touch_GestureCallBack callbackGesture ) { if ( !CCLayer::init() ) { return false; } m_pClickCallBack = callbackClick; m_pGestureCallBack = callbackGesture; m_pTarget = pTarget; m_p2dEndTouch = new CCPoint(); setTouchEnabled(true); m_pGeometricRecognizer = new GeometricRecognizer(); m_pGeometricRecognizer->loadTemplates(); m_bClicked = false; m_pEffectRoot = CCNode::create(); this->addChild( m_pEffectRoot,10 ); return true; } void CTouchManager::registerWithTouchDispatcher() { CCDirector* pDirector = CCDirector::sharedDirector(); pDirector->getTouchDispatcher()->addTargetedDelegate(this, 0, false); } bool CTouchManager::ccTouchBegan( CCTouch *pTouch, CCEvent *pEvent ) { CCParticleSystemQuad* mpEffect = CCParticleSystemQuad::create("TouchEffect/TouchEffect.plist"); m_pEffectRoot->addChild(mpEffect); mpEffect->setPosition( 0, 0 ); EffectFollow( pTouch ); m_lClickedTime = MilliSecondNow(); return true; } void CTouchManager::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent){ EffectFollow( pTouch ); FingerRecord( pTouch ); } void CTouchManager::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent){ m_pEffectRoot->removeAllChildren(); CCPoint mTmp = pTouch->getLocationInView(); m_p2dEndTouch->x = mTmp.x; m_p2dEndTouch->y = mTmp.y; if( MilliSecondNow() - m_lClickedTime < 200 ){ if ( m_bClicked ) { DoubleClick(); } else { scheduleOnce(schedule_selector(CTouchManager::Click), 0.2f); m_bClicked = true; return; } } else{ FingerJudge(); } DoSomething(); } void CTouchManager::ccTouchCancelled( CCTouch *pTouch, CCEvent *pEvent ) { m_bClicked = false; m_p2dPath.clear(); } void CTouchManager::Click( float fTime ) { if( m_bClicked ){ m_nInputKind = E_INPUT_KIND_ONE_CLICK; m_bClicked = false; DoSomething(); } } void CTouchManager::DoubleClick() { m_bClicked = false; m_nInputKind = E_INPUT_KIND_DOUBLE_CLICK; } long CTouchManager::MilliSecondNow() { struct cc_timeval now; CCTime::gettimeofdayCocos2d(&now, NULL); return (now.tv_sec * 1000 + now.tv_usec / 1000); } void CTouchManager::FingerJudge() { if( m_p2dPath.size() < 1 ){ m_nInputKind = E_INPUT_KIND_ONE_CLICK; return; } RecognitionResult mResult = m_pGeometricRecognizer->recognize(m_p2dPath); m_nInputKind = mResult.name; } void CTouchManager::FingerRecord(CCTouch* pTouch) { CCPoint mLocation = pTouch->getLocationInView(); Point2D mPoint2DTmp; mPoint2DTmp.x = mLocation.x; mPoint2DTmp.y = mLocation.y; m_p2dPath.push_back(mPoint2DTmp); } void CTouchManager::DoSomething() { if( m_nInputKind == E_INPUT_KIND_ONE_CLICK || m_nInputKind == E_INPUT_KIND_DOUBLE_CLICK ){ (m_pTarget->*m_pClickCallBack)( m_nInputKind, m_p2dEndTouch ); } else{ (m_pTarget->*m_pGestureCallBack)( m_nInputKind ); } m_p2dPath.clear(); } void CTouchManager::EffectFollow( CCTouch* pTouch ) { CCPoint mTmp = pTouch->getLocation(); m_pEffectRoot->setPosition( mTmp.x, mTmp.y ); }
为了降低程序耦合度,方便大家使用,我在这里使用回调函数的方式来处理。关于回调函数的使用,不清楚的百度一下~~
简单的解释下主要函数的作用:
1.create( CCObject* pTarget, Touch_ClickCallBack callbackClick, Touch_GestureCallBack callbackGesture )以及
init( CCObject* pTarget, Touch_ClickCallBack callbackClick, Touch_GestureCallBack callbackGesture )
主要作用是初始化CTouchManager,由于其继承CCLayer,所以需要注意的是记得开启输入检测setTouchEnabled(true)
其次是我这里使用的虚函数方式。在我们需要用到手势检测的scene或者layer中,传入其对象以及其要被回调的函数来初始化CTouchManager类。
2.registerWithTouchDispatcher()
ccTouchBegan( CCTouch *pTouch, CCEvent *pEvent )
ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent)
ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent)
ccTouchCancelled( CCTouch *pTouch, CCEvent *pEvent )
这几个函数就不用多说了,继承CCLayer下来的几个cocos2d-x中相应单点输入的虚函数。我们主要在这几个函数中获取输入点。
a.在Began中,我加入了一个粒子效果方便在测试的时候观察绘制轨迹。
在其中,主要还记录了输入按下的系统时间,方便单击、双击与画形状的判断
b.在Move中,这里是手势判断的重点所在,主要在这里获取玩家绘制轨迹的点集。(同时让粒子跟随移动)
void CTouchManager::FingerRecord(CCTouch* pTouch) { CCPoint mLocation = pTouch->getLocationInView(); Point2D mPoint2DTmp; mPoint2DTmp.x = mLocation.x; mPoint2DTmp.y = mLocation.y; m_p2dPath.push_back(mPoint2DTmp); }获得点集后将其压入我们自定义的对象中(其实就是个vector)
c.在End中,主要就是判断手势结果了。单击、双击、以及图形判断。这里双击、单击的判断使用了一个cocos2dx中的一个延时处理函数scheduleOnce。它的主要作用就是用来做双击和单击的判断。简单的说,就是要在单击后的200毫秒以内再次点击就算作双击,若没有,则作为单击判断。同事,若第一次按下和抬起时间大于200毫秒,则就进行手势形状的判断。(同时在这里记录了一个结束手势的点记录,主要用于单击和双击事件中可能需要得到点坐标来进行一些处理,比如移动或者向目标点释放技能等等)
手势判断最后是需要DoSomething
void CTouchManager::DoSomething() { if( m_nInputKind == E_INPUT_KIND_ONE_CLICK || m_nInputKind == E_INPUT_KIND_DOUBLE_CLICK ){ (m_pTarget->*m_pClickCallBack)( m_nInputKind, m_p2dEndTouch ); } else{ (m_pTarget->*m_pGestureCallBack)( m_nInputKind ); } m_p2dPath.clear(); }这里就是在初始化时传入的对象来调用回调函数。之后做什么事情就根据你的具体需求在对应对象中具体实现了。
基本的使用就是这样,在代码压缩包中有使用的例子。
总结:这篇手势识别主要是建立在$1Unistroke Recognizer现成的手势算法基础上的使用,其实技术含量并不高,主要是帮助大家更快速的理解和使用它。