您的位置:首页 > 移动开发 > Cocos引擎

cocos2d-x v3.2 触摸事件派发机制

2014-07-31 21:38 302 查看
在使用时,首先创建一个事件监听器,事件监听器包含以下几种:

多点触摸事件 EventListenerTouchAllAtOnce

单点触摸事件 EventListenerTouchOneByOne

键盘响应事件 EventListenerKeyboard

加速记录事件 EventListenerAcceleration

鼠标响应事件 EventListenerMouse

自定义事件 EventListenerCustom

以上事件监听器统一由 _eventDispatcher 来进行管理。它的工作需要三部分组成:

事件分发器 EventDispatcher

事件类型 EventTouch, EventKeyboard 等

事件监听器 EventListenerTouch, EventListenerKeyboard 等

监听器实现了各种触发后的逻辑,在适当时候由 事件分发器分发事件类型,然后调用相应类型的监听器。

一、触摸事件

现在将要实现在一个界面中添加三个按钮,三个按钮将会互相遮挡,并且能够触发触摸事件,以下是具体实现,首先创建三个精灵,作为三个按钮的显示图片及注释都在里面:

单点触摸:

[cpp] view
plaincopy





void HelloWorld::addSprites()

{

Point origin(200,50);



Size size(100,100);



auto sprite1 = Sprite::create("CyanSquare.png");

sprite1->setPosition(origin+Point(size.width/2, size.height/2) + Point(-80, 80));

addChild(sprite1, 10);



auto sprite2 = Sprite::create("MagentaSquare.png");

sprite2->setPosition(origin+Point(size.width/2, size.height/2));

addChild(sprite2, 20);



auto sprite3 = Sprite::create("YellowSquare.png");

sprite3->setPosition(Point(0, 0));

sprite2->addChild(sprite3, 1);



// 创建一个事件监听器 OneByOne 为单点触摸

auto listener1 = EventListenerTouchOneByOne::create();



// 设置是否吞没事件,在 onTouchBegan 方法返回 true 时吞没

listener1->setSwallowTouches(true);



// 使用 lambda 实现 onTouchBegan 事件回调函数

listener1->onTouchBegan = [](Touch* touch, Event* event)

{

// 获取事件所绑定的 target

auto target = static_cast<Sprite*>(event->getCurrentTarget());



// 获取当前点击点所在相对按钮的位置坐标

// touch-getLocation返回的是open GL的坐标系

Point locationInNode = target->convertToNodeSpace(touch->getLocation());



Size s = target->getContentSize();



Rect rect = Rect(0, 0, s.width, s.height);



// 点击范围判断检测

if (rect.containsPoint(locationInNode))

{

log("sprite began... x = %f, y = %f", locationInNode.x, locationInNode.y);

//鼠标按下时,设置一点透明,模拟按下效果

target->setOpacity(180);

return true;

}

return false;

};



// 触摸移动时触发

listener1->onTouchMoved = [](Touch* touch, Event* event)

{

auto target = static_cast<Sprite*>(event->getCurrentTarget());



// 移动当前按钮精灵的坐标位置

// touch-getDelta()返回当前坐标物体最初坐标的差值,这样能保证物体实时在移动,动作很连贯

target->setPosition(target->getPosition() + touch->getDelta());

};



// 点击事件结束处理

listener1->onTouchEnded = [=](Touch* touch, Event* event)

{

auto target = static_cast<Sprite*>(event->getCurrentTarget());

log("sprite onTouchesEnded.. ");

//设置透明度为255

target->setOpacity(255);

// 重新设置 ZOrder,显示的前后顺序将会改变

// 是的,你没有看错,sprite2出现了,我一直以为lambda表达式只是匿名函数名

// 原来在同一个函数内外面的变量在lambda表达式中还有效。

if (target == sprite2)

{

sprite1->setZOrder(100);

}

else if(target == sprite1)

{

sprite1->setZOrder(0);

}

};



// 添加监听器 _eventDispatcher是node的成员变量,他不是单例,但是他是Director中一个成员变量

// _eventDispatcher = director->getEventDispatcher();由于Director是单例,所以_eventDispatcher

// 获得的就相当于单例对象

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite1);

//clone 是深度复制

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite2);



_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite3);

}

多点触摸:

auto dispatcher = Director::getInstance()->getEventDispatcher();
        	auto listener = EventListenerTouchAllAtOnce::create();
        	listener->onTouchesBegan = CC_CALLBACK_2(PlayMusic::onTouchesBegan, this);
        	listener->onTouchesMoved = CC_CALLBACK_2(PlayMusic::onTouchesMoved, this);
        	listener->onTouchesEnded = CC_CALLBACK_2(PlayMusic::onTouchesEnded, this);
        	listener->onTouchesCancelled = CC_CALLBACK_2(PlayMusic::onTouchesCancelled, this);
        	dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
         四个回调函数分别对应: 
         
        1.手指按下 
        2.手指划动 
        3.手指松开 
        4.意外中断触摸(弹窗之类的) 
          
        四个函数的原型为: 
         
        	void onTouchesBegan(const std::vector<cocos2d::Touch*>& touches, cocos2d::Event* event);
        	void onTouchesMoved(const std::vector<cocos2d::Touch*>& touches, cocos2d::Event* event);
        	void onTouchesEnded(const std::vector<cocos2d::Touch*>& touches, cocos2d::Event* event);
        	void onTouchesCancelled(const std::vector<cocos2d::Touch*>& touches, cocos2d::Event* event);



注意,如果写成这种lambda表达的形式,三个触摸函数就不需要在头文件里面声明了。

添加事件监听器到事件分发器:

[cpp] view
plaincopy





<pre name="code" class="cpp"> // 添加监听器 _eventDispatcher是node的成员变量,他不是单例,但是他是Director中一个成员变量

// _eventDispatcher = director->getEventDispatcher();由于Director是单例,所以_eventDispatcher

// 获得的就相当于单例对象

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite1);

//clone 是深度复制

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite2);



_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite3);



_eventDispatcher 是 Node 的属性,通过它管理当前节点(如 场景 、层、精灵等 )的所有事件分发情况。但是它本身是一个单例模式值的引用,在 Node 构造函数中,通过 "Director::getInstance()->getEventDispatcher();" 获取,有了这个属性,我们能更为方便的调用。

注意: 这里当我们再次使用 listener1 的时候,需要使用 clone() 方法创建一个新的克隆,因为在使用addEventListenerWithSceneGraphPriority 或者 addEventListenerWithFixedPriority 方法时,会对当前使用的事件监听器添加一个已注册的标记,

这使得它不能够被添加多次,FixedPriority(非0价值,如果是0那就是SceneGraphPriority listener) listener添加完之后需要手动remove,而SceneGraphPriority listener是跟node绑定的,在node的析构函数中会被移除。

下面看看运行效果图:



以上的步骤看似相对 2.x 版本触摸机制实现时,复杂了点,在老的版本中继承一个 delegate ,里面定义了 onTouchBegan 等方法,然后在里面判断点击的元素,进行逻辑处理。而这里将事件处理逻辑独立出来,封装到一个 Listener 中,而以上的逻辑

实现了以下功能:

通过添加事件监听器,将精灵以显示优先级 (SceneGraphPriority) 添加到事件分发器。这就是说,当我们点击精灵按钮时,根据屏幕显示的“遮盖”实际情况,进行有序的函数回调(即:如图中***按钮首先进入 onTouchBegan 逻辑处理)。

在事件逻辑处理时,根据各种条件处理触摸后的逻辑,如点击范围判断,设置被点击元素为不同的透明度,达到点击效果。

因为设置了 listener1->setSwallowTouches(true); 并且在 onTouchBegan 中做相应的判断,以决定其返回值是 false 还是 true,用来处理触摸事件是否依据显示的顺序关系向后传递。

注意:与 SceneGraphPriority 所不同的是 FixedPriority 将会依据手动设定的 Priority 值来决定事件相应的优先级,值越小优先级越高。

其它事件派发处理模块, 除了触摸事件响应之外,还有以下模块使用了相同的处理方式。

二、键盘响应事件

除了键盘,还可以是终端设备的各个菜单,他们使用同一个监听器来进行处理:

在头文件中添加键盘两函数

[cpp] view
plaincopy





//键盘

void onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event);

void onKeyReleased(EventKeyboard::KeyCode keyCode, Event* event);

在源文件中添加代码:

[cpp] view
plaincopy





<pre name="code" class="cpp"> auto label = LabelTTF::create("KeyBoard test","Arial", 35);

label->setPosition(Point(240,500));

this->addChild(label);

// 初始化并绑定

auto keyBoardListener = EventListenerKeyboard::create();

keyBoardListener->onKeyPressed = CC_CALLBACK_2(HelloWorld::onKeyPressed, this);

keyBoardListener->onKeyReleased = CC_CALLBACK_2(HelloWorld::onKeyReleased, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(keyBoardListener, this);



[cpp] view
plaincopy





// 键位响应函数原型

void HelloWorld::onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event)

{

log("Key with keycode %d pressed", keyCode);

}



void HelloWorld::onKeyReleased(EventKeyboard::KeyCode keyCode, Event* event)

{

log("Key with keycode %d released", keyCode);

}

运行后,随便按键盘上按钮,会输出相应的key值。

三、加速计事件

在使用加速计事件监听器之前,需要先启用此硬件设备:

[cpp] view
plaincopy





Device::setAccelerometerEnabled(true);

然后创建对应的监听器,在创建回调函数时,可以使用 lambda 表达式创建匿名函数,也可以绑定已有的函数逻辑实现,如下:

[cpp] view
plaincopy





//加速计

m_ball = Sprite::create("ball.png");

m_ball->setPosition(Point(400, 600));

this->addChild(m_ball);

//在使用加速计事件监听器之前,需要先启用此硬件设备

Device::setAccelerometerEnabled(true);

//EventListenerAcceleration只支持这一种create方式

auto accelerListener = EventListenerAcceleration::create(CC_CALLBACK_2(HelloWorld::onAcceleration, this));

_eventDispatcher->addEventListenerWithSceneGraphPriority(accelerListener, this);



void HelloWorld::onAcceleration(Acceleration* acc, Event* unused_event)

{

if( m_ball == NULL )

{

log("ball is NULL");

return ;

}



//dosomething



}

由于在windows上没有重力设备,所以看不到效果,但是代码基本就是这样的

四、鼠标响应事件

在 3.0 中多了鼠标捕获事件派发,这可以在不同的平台上,丰富我们游戏的用户体验。

[cpp] view
plaincopy

mouseListener = EventListenerMouse::create();

mouseListener->onMouseMove = CC_CALLBACK_1(HelloWorld::onMouseMove, this);

mouseListener->onMouseUp = CC_CALLBACK_1(HelloWorld::onMouseUp, this);

mouseListener->onMouseDown = CC_CALLBACK_1(HelloWorld::onMouseDown, this);

mouseListener->onMouseScroll = CC_CALLBACK_1(HelloWorld::onMouseScroll, this);



_eventDispatcher->addEventListenerWithSceneGraphPriority(mouseListener, this);

使用如上方法,创建一个鼠标监听器。然后分别实现各种回调函数,并且绑定。

[cpp] view
plaincopy

void HelloWorld::onMouseDown(Event *event)

{

EventMouse* e = (EventMouse*)event;

string str = "Mouse Down detected, Key: ";

str += tostr(e->getMouseButton());

// ...

}





void HelloWorld::onMouseUp(Event *event)

{

EventMouse* e = (EventMouse*)event;

string str = "Mouse Up detected, Key: ";

str += tostr(e->getMouseButton());

// ...

}





void HelloWorld::onMouseMove(Event *event)

{

EventMouse* e = (EventMouse*)event;

string str = "MousePosition X:";

str = str + tostr(e->getCursorX()) + " Y:" + tostr(e->getCursorY());

// ...

}





void HelloWorld::onMouseScroll(Event *event)

{

EventMouse* e = (EventMouse*)event;

string str = "Mouse Scroll detected, X: ";

str = str + tostr(e->getScrollX()) + " Y: " + tostr(e->getScrollY());

// ...

}

五、自定义事件

以上是系统自带的事件类型,事件由系统内部自动触发,如 触摸屏幕,键盘响应等,除此之外,还提供了一种 自定义事件,简而言之,

它不是由系统自动触发,而是人为的干涉,如下:

[cpp] view
plaincopy

userListener = EventListenerCustom::create("game_custom_event1", [=](EventCustom* event)

{

std::string str("Custom event 1 received, ");

char* buf = static_cast<char*>(event->getUserData());

str += buf;

str += " times";

statusLabel->setString(str.c_str());

});



_eventDispatcher->addEventListenerWithFixedPriority(userListener, 1);

以上定义了一个 “自定义事件监听器”,实现了一些逻辑,并且添加到事件分发器。那么以上逻辑是在什么情况下响应呢?请看如下:

[cpp] view
plaincopy





<pre name="code" class="cpp"> static int count = 0;

++count;

char* buf = new char[10];

sprintf(buf, "%d", count);

EventCustom event("game_custom_event1");

event.setUserData(buf);

_eventDispatcher->dispatchEvent(&event);



定义了一个 EventCustom ,并且设置了其 UserData 数据,手动的通过 _eventDispatcher->dispatchEvent(&event); 将此事件分发出去,从而触发之前所实现的逻辑。

六、移除事件监听器

我们可以通过以下方法移除一个已经被添加了的监听器。

[cpp] view
plaincopy





_eventDispatcher->removeEventListener(listener);

也可以使用如下方法,移除当前事件分发器中所有监听器。

[cpp] view
plaincopy





_eventDispatcher->removeAllEventListeners();

当使用 removeAll 的时候,此节点的所有的监听将被移除,推荐使用 指定删除的方式。

注意:removeAll 之后 菜单 也不能响应。因为它也需要接受触摸事件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: