监听器(Listener)
当我们需要某些节点,有一些交互事件时,可以使用监听器,当有了监听器,对应的节点才能响应一些用户的操作
六种类型
EventListenerTouch
- 响应触摸事件EventListenerKeyboard
- 响应键盘事件EventListenerAcceleration
- 响应加速度事件(比如移动手机重力加速)EventListenMouse
- 响应鼠标事件EventListenerFocus
- 聚焦事件EventListenerCustom
- 响应自定义事件
事件的吞没
当你有一个监听器,已经接收到了期望的事件,这时事件应该被吞没。事件被吞没,意味着在事件的传递过程中,你消耗了此事件,事件不再向下传递。避免了下游的其它监听器获取到此事件。
设置吞没:
// 一个监听器可以被设置到多个节点上,假如现在有两个精灵重叠在一起,都有同一个触摸事件
// 那么当setSwallowTouches设置为true时, 一但监听器在某一个地方return true
// 那么这个监听器事件就会被吞没,
// 并防止重叠在下一层的精灵不会响应该监听事件
listener1->setSwallowTouches(true);
// 在onTouchBegan()应该返回true
listener1->onTouchBegan = [](Touch* touch, Event* event){
// 代码
return true;
};
监听器优先级
固定值优先级
创建时使用一个整型数值,数值较低的监听器比数值较高的先接收到事件
//场景图优先级
void addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority);
//fixedPriority参数表示优先级,指定某个节点上响应事件的优先级。
我们知道,`cocos`的节点是以树状保存的,中序遍历中z-order较高的监听器节点在顶部绘制,这种优先级确保触摸事件可以被先后响应,即谁在图层上方,谁最先响应(如果fixedPriority想同的话)<br />如果我们只想要这种前后关系的优先级,那么用下面这种方法
场景图层分发器
void addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node);
创建一个简单的单点触摸监听器
这种监听器一次只能响应屏幕上一个点的触碰,与我们手机上双指放大的触碰会有区别
创建单点触摸监听器
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true); //设置事件吞没
//如果想要多点触摸,那么可以使用
EventListenerTouchAllAtOnce();
//并且对应响应函数中的传参为vector<Touch*>类型,并且多点触摸的onTouchesBegan的返回为void
//(注意和单点触摸不一样,比如不再是onTouch,而是OnTouchesBegan)
此监听器包含四个触发时机
onTouchBegan
触摸开始时onTouchMoved
触摸点移动时onTouchEnded
触摸结束时onTouchCancelled
触摸取消时
四种触发时机对应的回调函数
listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::touchBegin,this);
listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::touchMove,this);
listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::touchEnd,this);
listener->onTouchCancelled = CC_CALLBACK_2(HelloWorld::touchEnd,this);
回调函数包含的参数与返回值
bool HelloWorld::touchBegin(Touch *touch, Event *event){
return true;
}
void HelloWorld::touchMove(Touch *touch, Event *event){
}
void HelloWorld::touchEnd(Touch *touch, Event *event){
}
分发器
当我们有了一个监听器时,我们还需要一个分发器,用来给相关的节点注册监听器。
创建事件分发器
//eventDispatcher是Node的属性,通过它管理当前节点(场景、层、精灵)的所有事件的分发
EventDispatcher* eventDispatcher = Director::getInstance()->getEventDispactcher();
利用分发器注册监听器
//注册监听器到精灵,让监听器监听发生再精灵上的触摸事件
eventDispatcher->addEventListenerWithSceneGraphPriority(listener, sprite)
//WithSceneGraphPriority表示使用场景图优先级
//每个事件监听器只能被添加一次
//假如一个事件监听器需要重复利用,可以用clone()新建一个即可
eventDispatcher->addEventListenerWithSceneGraphPriority(listener->clone(),sprite2)
移除监听器
eventDispatcher->removeEventListener(listener); //移除指定监听器
eventDispatcher->removeAllEventListener(); //移除所有监听器
创建一个可移动精灵的案例
现在,我们需要一个精灵,这个精灵可以被我们使用鼠标拖动,那么首先,我们需要一个精灵,以及在它身上注册一个触摸监听器
//创建一个可移动图标
auto icon = Sprite::create("icons8_adobe_photoshop_96px.png");
icon->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 - 20));
icon->setAnchorPoint(Point(0.5, 0.5));
this->addChild(icon, 6);
//创建监听器
auto iconMoveListener = EventListenerTouchOneByOne::create();
iconMoveListener->setSwallowTouches(true);
//给监听器挂载对应的回调函数
iconMoveListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::touchBegin, this);
iconMoveListener->onTouchMoved = CC_CALLBACK_2(HelloWorld::touchMove, this);
iconMoveListener->onTouchEnded = CC_CALLBACK_2(HelloWorld::touchEnd, this);
//创建分发器
EventDispatcher* eventDispatcher = Director::getInstance()->getEventDispatcher();
//采用场景图层优先级的方式注册监听器
eventDispatcher->addEventListenerWithSceneGraphPriority(iconMoveListener, icon);
//目前为止,我们的icon精灵上就已经有了一个监听器,它可以监听三种状态
注册完毕之后,我们还需要完善每种状态下的响应事件
//第一种状态
bool HelloWorld::touchBegin(Touch *touch, Event *event) {
//因为是在回调函数中,所以只能从传参中获得我们的目标精灵,相关信息保存在event中
//因为精灵也是一个树中的特殊节点,所以我们若要获取精灵,可以使用static_cast强制转换
auto target = static_cast<Sprite *>(event->getCurrentTarget());
//另外,在之前代码中,我们知道,精灵有可能是另一个节点的子节点,它的坐标可能并不是相对于屏幕而言的
//但是,触摸touch的坐标,是按照屏幕的坐标系而言的,所以,我们需要转换
//有几种方式,下面这行代码的意思是,将touch的坐标转化为target节点所在的坐标系中
Vec2 touchLocalPosition = target->convertToNodeSpace(touch->getLocation());
//直接获得target的世界坐标系
//Vec2 targetWorldPostion = target->getPosition();
//将target的世界坐标系转换成相对坐标系
//Vec2 targetLocalPosition = target->convertToNodeSpace(targetWorldPostion);
//在cocos中,这只是简单的触碰,cocos并不能确定是否触碰到了精灵的大小范围内,这需要我们自行判断
//首先,获取目标节点的大小
Size size = target->getContentSize();
//然后,创建一个矩形区域
Rect rect = Rect(0, 0, size.width, size.height);//四个参数分别对应左上和右下的坐标
//然后利用containsPoint函数来判断是否触碰在该精灵范围内,是返回1,不是则返回0
if (rect.containsPoint(touchLocalPosition)) {
//如果触碰到了精灵,则精灵稍稍放大一下
target->setScale(1.2f);
return true; //事件吞没,防止下一层监听器继续被触发
}
return false;
}
//第二种状态
void HelloWorld::touchMove(Touch *touch, Event *event) {
//该状态自然是要将精灵移动起来
auto target = static_cast<Sprite *>(event->getCurrentTarget());//我们继续获得该精灵
target->setPosition(target->getPosition() + touch->getDelta());//Delta是变化量,Vec2有运算符重载,支持+号
}
//第三种状态
void HelloWorld::touchEnd(Touch *touch, Event *event) {
//表示以及完成触摸移动,需要将节点还原大小
auto target = static_cast<Sprite *>(event->getCurrentTarget());
target->setScale(1.0f);
}
现在,该精灵就可以被我们用鼠标移动来移动去了
鼠标事件
除了触碰的监听器以外,我们也可以尝试监听鼠标事件,与触碰事件不同的是,鼠标事件中,少了目标节点的信息,只能监听鼠标移入,鼠标悬浮,鼠标按下,鼠标滑轮滚动等具体事件,不过可以依次制作射击游戏中的开火,瞄准等功能。
创建鼠标事件监听器
_mouseListener = EventListenerMouse::create();
_mouseListener->onMouseMove = CC_CALLBACK_1(MouseTest::onMouseMove, this); //几种监听状态
_mouseListener->onMouseUp = CC_CALLBACK_1(MouseTest::onMouseUp, this);
_mouseListener->onMouseDown = CC_CALLBACK_1(MouseTest::onMouseDown, this);
_mouseListener->onMouseScroll = CC_CALLBACK_1(MouseTest::onMouseScroll, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(_mouseListener, this);
具体函数中可以完成的部分
void MouseTest::onMouseDown(Event *event)
{
// 样例
EventMouse* e = (EventMouse*)event; //获取鼠标事件
string str = "Mouse Down detected, Key: ";
str += tostr(e->getMouseButton()); //可以检测鼠标的哪个按键被按下
}
void MouseTest::onMouseUp(Event *event)
{
// 样例
EventMouse* e = (EventMouse*)event;
string str = "Mouse Up detected, Key: ";
str += tostr(e->getMouseButton()); //可以检测鼠标的哪个按键被抬起
}
void MouseTest::onMouseMove(Event *event)
{
// 样例
EventMouse* e = (EventMouse*)event;
string str = "MousePosition X:";
str = str + tostr(e->getCursorX()) + " Y:" + tostr(e->getCursorY()); //可以获得鼠标的位置
}
void MouseTest::onMouseScroll(Event *event)
{
// 样例
EventMouse* e = (EventMouse*)event;
string str = "Mouse Scroll detected, X: ";
str = str + tostr(e->getScrollX()) + " Y: " + tostr(e->getScrollY()); //滚动信息
}