在移动平台中设备与用户的交互必须通过事件处理完成。用户输入被封装为事件,Cocos2d-x游戏引擎能够接收并处理这些事件,包括触摸事件、键盘事件、鼠标事件、加速度事件和自定义事件等。这里需要注意,不同平台之间事件略有差异,这与硬件有关,如在iOS
平台就无法接收键盘事件,在PC和Mac OS X平台无法接收触摸事件和加速度事件。
Cocos2d-x 用户事件处理机制
一般来说,事件处理机制都有三个角色:事件、事件源和事件处理,其中事件源是事件发生的场所,通常指控件或视图;事件处理者是接收事件并对其进行响应处理。
(1) 事件:
事件类是Event,它的子类主要有:
EventTouch(触摸事件)
EventKeyBoard(键盘事件)
EventMouth(鼠标事件)
EventAcceleration(加速度事件)
EventCustom(自定义事件)
(2) 事件源:
指的是Cocos2d-x中的精灵、层和菜单等节点对象。
(3) 事件处理:
Cocos2d-x中的事件处理者是事件监听类EventListener,它的子类主要有:
EventListenerTouchOneByOne(单点触摸监听器)
EventListenerTouchAllAtOnce(多点触摸监听器)
EventListenerKeyBoard(键盘事件监听器)
EventListenerMouse(鼠标事件监听器)
EventListenerAcceleration(加速度事件监听器)
EventListenerCustom(自定义事件监听器)
1.事件分发器
事件的监听和事件具有对应关系,Cocos2d-x提供了一个事件分发器(EventDispatcher)用于管理这种关系。
EventDispatcher类采用的是单例模式,可以通过Director::getInstance()->getEventDispatcher()获得事件分发对象。
EventDispatcher类中注册事件监听器到事件的函数如下:
(1)指定固定的事件优先级注册监听器,事件优先级决定事件响应的优先级,值越小优先级越高。
void addEventListenerWithFixedPriority(EventListener * listener,int fixedPriority)
(2)把一些控件的显示优先级作为事件优先级,越上层的精灵或层越先响应事件。
void addEventListenerWithSceneGraPHPriority(EventListener * listener,Node * node)
当不再进行事件响应时,需要注销事件监听器。主要的注销函数如下:
(1)注销指定事件监听器
void removeEventListener(EventListener * listener)
(2)注销自定义事件监听器
void removeCustomEventListener(EventListener * listener)
(3)注销所有事件监听,注意:使用该函数之后,菜单也不能响应事件了,因为它需要接收触摸事件。
void removeAllEventListeners(EventListener * listener)
2.事件
下面主要介绍五类事件:
2.1 触摸事件
触摸事件可以有“按下”、“移动”和“抬起”的阶段,表示触摸的开始、移动和结束状态。另外,触摸事件的不同阶段都有单点触摸和多点触摸,具体还得看平台和设备。
(1)EventListenerTouchOneByOne(单点触摸监听器)
单点触摸事件中的响应属性:
1)当一个手指触摸屏幕时回调该属性的指定函数。如果返回为true,则可以回调后面两个属性所指定的函数,否则不回调。
std::function<bool(Touch *,Event *)> onTouchBegan
std::function<void(Touch *,Event *)> onTouchMoved
std::function<void(Touch *,Event *)> onTouchEnded
std::function<void(Touch *,Event *)> onTouchCancelled
(2)EventListenerTouchAllAtOnce(多点触摸监听器)
多点触摸事件中的响应属性:
1)当多个手指触摸屏幕时回调该属性的指定函数。如果返回为true,则可以回调后面两个属性所指定的函数,否则不回调。
std::function<void(const std::vector<Touch * >&,Event *)> onTouchesBegan
std::function<void(const std::vector<Touch * >&,Event *)> onTouchesMoved
std::function<void(const std::vector<Touch * >&,Event *)> onTouchesEnded
std::function<void(const std::vector<Touch * >&,Event *)> onTouchesCancelled
另外,每个触摸点(touch)对象包含了当前位置信息,以及之前的位置信息。
下面的函数是可以获得触摸点之前的位置信息:
Vec2 getPrevIoUsLocationInView() //UI坐标
Vec2 getPrevIoUsLocation() //OpenGL坐标
下面的函数是可以获得触摸点当前的位置信息:
Vec2 getLocationInView() //UI坐标
Vec2 getLocation() //OpenGL坐标
接下来是一个简单单点触摸示例:
//一般来说监听器在onEnter时注册
void HelloWorld::onEnter()
{
Layer::onEnter();
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::touchBegan,this);
listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::touchMoved,this);
listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::touchEnded,this);
EventDispatcher * eventDispatcher = Director::getInstance()->getEventDispatcher();
//参数为Node类型,故需要用getChildByTag函数获取节点
eventDispatcher->addEventListenerWithGraPHPriority(listener,getChildByTag(tagA));
/* 当一个事件监听器需要重复利用时,需要用clone()方法创建一个新的克隆,因为在使用addEventListenerWithSceneGraPHPriorith或者addEventListenerWithFixedPriority方法进行注册时,会对当前使用的事件监听器添加一个已注册的标记,这使得它不能够被添加多次。 */
eventDispatcher->addEventListenerWithGraPHPriority(listener->clone(),getChildByTag(tagB));
eventDispatcher->addEventListenerWithGraPHPriority(listener->clone(),getChildByTag(tagC));
}
bool HelloWorld::touchBegan(Touch * touch,Event * event)
{
auto target = static_cast<Sprite *>(event->getCurrentTarget());
Vec2 locationInNode = target->convertToNodeSpace(touch->getLocation());
Size s = target->getContentSize();
Rect rect = Rect(0,0,s.width,s.height);
//判断单击是否在范围内
if(rect.containsPoint(locationInNode))
{
target->runAction(ScaleBy::create(1.5f,1.5f));
...
return true;//吞没事件
}
return false;
}
void HelloWorld::touchMoved(Touch * touch,Event * event)
{
auto target = static_cast<Sprite *>(event->getCurrentTarget());
target->setPosition(target->getPosition() + touch->getDelta());
}
void HelloWorld::touchEnded(Touch * touch,s.height);
if(rect.containsPoint(locationInNode))
{
target->runAction(ScaleTo::create(1.0f,1.0f));
...
return true;
}
}
其中,CC_CALLBACK_2(HelloWorld::touchBegan,this) 是使用CC_CALLBACK_2宏绑定回调函数。
2.2 键盘事件
键盘事件监听器是EventListenerKeyboard,其中的事件响应属性有:
std::function<void(EventKeyboard::KeyCode,Event *)> onKeyPressed
std::function<void(EventKeyboard::KeyCode,Event *)> onKeyReleased
键盘事件处理的代码片段如下,这里使用Lambda表达式,之后会介绍:
void HelloWorld::onEnter()
{
Layer::onEnter();
auto listener = EventListenerKeyboard::create();
//EventKeyboard::KeyCode 是按键号
listener->onKeyPressed = [](EventKeyboard::KeyCode keyCode,Event * event)
{
...
};
listener->onKeyReleased = [](EventKeyboard::KeyCode keyCode,Event * event)
{
...
};
EventDispatcher * eventDispatcher = Director::getInstance()->getEventDispatcher();
eventDispatcher->addEventListenerWithGraPHPriority(listener,this);
}
2.3 鼠标事件
鼠标事件监听器是EventListenerMouse,其中的事件响应属性有:
(1)当鼠标键按下时回调该属性所指定的函数。
std::function<void(Event * event)> onMouseDown
std::function<void(Event * event)> onMouseMove
std::function<void(Event * event)> onMouseScroll
std::function<void(Event * event)> onMouseUp
鼠标事件处理的代码片段如下,这里使用Lambda表达式,之后会介绍:
void HelloWorld::onEnter()
{
Layer::onEnter();
auto listener = EventListenerMouse::create();
listener->onMouseMove = [](Event * event)
{
EventMouse * e = (EventMouse *)event;
...
};
listener->onMouseUp = [](Event * event)
{
EventMouse * e = (EventMouse *)event;
...
};
listener->onMouseScroll = [](Event * event)
{
EventMouse * e = (EventMouse *)event;
...
};
listener->onMouseDown = [](Event * event)
{
EventMouse * e = (EventMouse *)event;
...
};
EventDispatcher * eventDispatcher = Director::getInstance()->getEventDispatcher();
eventDispatcher->addEventListenerWithGraPHPriority(listener,this);
}
2.4 加速度事件
大多数的移动设备都支持加速度计传感器,加速度计传感器是一种能够感知设备一个方向上线性加速度的传感器。iOS和安卓设备三轴加速度计的坐标系是右手坐标系。
加速度计三维坐标系
使用事件分发器之前需要注册加速度事件监听器,在此之前还需先启用此设备硬件设备:
Device::setAccelerometerEnabled(true);
加速度事件监听器是EventListenerAcceleration,与其他事件监听器不同的是,它没有事件响应属性,事件响应是通过静态create指定的。代码如下:
static EventListenerAcceleration * create(std::function<void(Acceleration *,Event *)>callback)
下面是示例片段:
void HelloWorld::onEnter()
{
Layer::onEnter();
Device::setAccelerometerEnabled(true);
auto listener = EventListenerAcceleration::create([](Acceleration * acc,Event * event)
{
...
});
EventDispatcher * eventDispatcher = Director::getInstance()->getEventDispatcher();
eventDispatcher->addEventListenerWithGraPHPriority(listener,this);
}
2.5 自定义事件
上面的事件类型是系统已经定义了的,而且事件(例如触摸屏幕,按键响应)是由系统自动触发的。另外,你可以制作你自己的custom events ,它们可以不由系统触发,但是必须通过你的代码,如下:
_listener = 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(_listener,1);
上面自定义了一个事件监听器,并且带有一个响应方法。它被添加到了事件分发器里。
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);
CC_SAFE_DELETE_ARRAY(buf);
上面的例子创建了一个 EventCustom 对象,并且设置了他的 UserData。之后,它被 _eventDispatcher->dispatchEvent(&event);手动的分发。它将触发之前定义好的事件处理方法。
3.Lambda表达式
在Cocos2d-x 3.0之后提供了对C++11标准的支持,其中的Lambda表达式使用起来十分简洁。
对上一节的代码进行替换:
listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan,this);
...
bool HelloWorld::onTouchBegan(Touch * touch,Event * event)
{
....
return false;
}
替换为:
listener->onTouchBegan = [](Touch * touch,Event * event)
{
...
return false;
};
上面语句中的[](Touch * touch,Event * event){…}就是Lambda表达式,Lambada表达式就是JavaScript语言中的匿名函数,它可以直接声明函数而不需要独立声明。
替换为Lambda表达式之后之前的头文件代码也可以修改的比较简洁。
4.在层中进行的事件处理
层是节点的派生类,可以在层中响应事件处理,Cocos2d-x中为Layer又定义了一些与事件相关的函数。
单点触摸相关函数:
(1)bool onTouchBegan(Touch * touch,Event * event)
(2)void onTouchCancelled(Touch * touch,Event * event)
(3)void onTouchEnded(Touch * touch,Event * event)
(4)void onTouchMoved(Touch * touch,Event * event)
多点触摸相关函数:
(1)void onTouchesBegan(const std::vector<Touch * >&touches,Event * event)
(2)void onTouchesCancelled(const std::vector<Touch * >&touches,Event * event)
(3)void onTouchesEnded(const std::vector<Touch * >&touches,Event * event)
(4)void onTouchesMoved(const std::vector<Touch * >&touches,Event * event)
(1)void onKeyPressed(EventKeyboard::KeyCode keyCode,Event * event)
(2)void onKeyReleased(EventKeyboard::KeyCode keyCode,Event * event)
加速度事件相关函数:
void onAcceleration(Acceleration * acc,Event * event)
注意,若使用层的事件处理,需要在层的头文件中定义相关函数,由于是重写,需要在前面加上virtual关键字,且函数名需与父类一致。
有关事件的初始化可以放在onEnter函数或者init函数中,还需加入以下代码:
//使层开始支持触摸事件
setTouchEnabled(true);
//设置单点触摸,多点触摸是Touch::DispatchMode::ALL_AT_ONCE
setTouchMode(Touch::DispatchMode::ONE_BY_ONE);
层中的触摸事件使用起来比较简单,由于事件监听的对象是层,而不是层中的精灵等对象,当判断是否单击了哪个精灵对象时比较麻烦,需要使用Rect的containsPoint函数逐一判断每个精灵对象。
可以看出,层中的事件处理比较简单,不需要注册事件监听器,不需要指定它的事件响应优先级,这种优点也是它的缺点,当处理多个精灵的触摸事件时就有些繁琐。
总结