cocos2d-x v3.3的事件处理机制由事件类、事件监听器类以及事件分发器类三部分组成。事件类存储事件的相关信息,事件监听器类存储事件触发后所要执行的操作(事件的回调函数)的相关信息,事件分发器类负责监听事件的触发并将事件传递给对应的事件监听器。
首先来看看事件类和事件监听器类的继承关系:
Event
|
—————————————————————————————————————————————
| | | | | | |
EventAcceleration EventController EventCustom EventFocus EventKeyboard EventMouse EventTouch
Event类下面继承了各种具体事件的子类。Event存储事件的通用信息,各个子类存储事件的特有信息。
EventListener
|
—————————————————————————————————————————————
| | | | | | | |
EventListenerAcceleration | EventListenerController | EventListenerCustom EventListenerFocus | EventListenerKeyboard
| | |
EventListenerMouse EventListenerTouchOneByOne EventListenerTouchAllAtOnce
EventListener类与Event类模式相同。EventListener存储事件监听器通用信息,各个子类存储事件监听器的特有信息。
EventMouse:
一、成员变量:
public:
enum class MouseEventType // 鼠标事件类型。
{
MOUSE_NONE, // 未知类型。
MOUSE_DOWN, // 鼠标按下。
MOUSE_UP, // 鼠标抬起。
MOUSE_MOVE, // 鼠标移动。
MOUSE_SCROLL, // 鼠标滚轮滑动。
};
private:
MouseEventType _mouseEventType; // 鼠标事件类型。
int _mouseButton; // 事件发生时哪个鼠标按键被操作(比如按下或抬起)。
float _x; // 事件发生时鼠标X轴坐标数据。
float _y; // 事件发生时鼠标Y轴坐标数据。
float _scrollX; // 事件发生时鼠标滚轮X轴滑动数据。
float _scrollY; // 事件发生时鼠标滚轮Y轴滑动数据。
bool _startPointCaptured; // 是否已经记录了事件发生时鼠标的起始坐标。
Vec2 _startPoint; // 事件发生时鼠标的起始坐标。
Vec2 _point; //
觉得与(_x, _y)功能相同,为什么要用两个变量重复记录?
Vec2 _prevPoint; // 上一次事件发生时鼠标的坐标。
friend class EventListenerMouse;
二、成员方法:
其中一些get/set的成员方法,就不一一列举了。
(1) EventMouse(MouseEventType mouseEventCode);
mouseEventCode:该类事件的具体哪个动作。
实现源码:
EventMouse::EventMouse(MouseEventType mouseEventCode)
: Event(Type::MOUSE) // 向其父类告知我是个鼠标事件(父类存储大的事件类型)。
, _mouseEventType(mouseEventCode) // 哪一个鼠标事件。
, _mouseButton(-1) // 这里无效的鼠标按键是-1,有效的按键可以在CCEventMouse.h的32~39行有宏定义。
, _x(0.0f)
, _y(0.0f)
, _scrollX(0.0f)
, _scrollY(0.0f)
//
还有一些变量没有初始化,为什么?
{
};
(2) inline void setCursorPosition(float x, float y);
当鼠标的某个按键被按下或抬起时,这个函数会被调用以记录相应的坐标信息。
实现源码:
inline void setCursorPosition(float x, float y)
{
_x = x;
_y = y;
_prevPoint = _point; // 保存上一次事件发生时鼠标的坐标。
_point.x = x;
_point.y = y;
if (!_startPointCaptured) // 如果事件刚刚发生(比如按住鼠标拖动之后抬起鼠标的操作),则需要记录鼠标的起始坐标(按下时的坐标)。
{
_startPoint = _point;
_startPointCaptured = true;
}
}
(3) Vec2 getLocation() const;
当事件发生时鼠标在OpenGL坐标系中的坐标。
Vec2 getPreviousLocation() const;
上一次事件发生时鼠标在OpenGL坐标系中的坐标。
Vec2 getStartLocation() const;
当事件发生时鼠标的起始坐标在OpenGL坐标系中的坐标。
Vec2 getDelta() const;
当事件发生时鼠标的起始坐标与当前坐标在OpenGL坐标系中的坐标差值。
Vec2 getLocationInView() const;
当事件发生时鼠标在屏幕坐标系中的坐标。
Vec2 getPreviousLocationInView() const;
上一次事件发生时鼠标在屏幕坐标系中的坐标。
Vec2 getStartLocationInView() const;
当事件发生时鼠标的起始坐标在屏幕坐标系中的坐标。
实现源码:
实现源码都很简单,就不一一列举了。
EventListenerMouse:
一、成员变量:
public:
static const std::string LISTENER_ID; // 监听器ID,注意这个ID是个字符串。
std::function<void(Event* event)> onMouseDown; // 当鼠标按下事件被触发时调用的回调函数。
std::function<void(Event* event)> onMouseUp; // 当鼠标抬起事件被触发时调用的回调函数。
std::function<void(Event* event)> onMouseMove; // 当鼠标移动事件被触发时调用的回调函数。
std::function<void(Event* event)> onMouseScroll; // 当鼠标滚轮滑动事件被触发时调用的回调函数。
每类监听器都有自己的ID(是个字符串),该ID在监听器子类中规定好,比如鼠标事件监听器的ID为“__cc_mouse”。
二、成员方法:
(1) static EventListenerMouse* create();
bool init();
创建鼠标事件监听器并初始化鼠标事件监听器的特有信息。
实现源码:
EventListenerMouse* EventListenerMouse::create()
{
auto ret = new (std::nothrow) EventListenerMouse(); // 创建鼠标监听器类。
if (ret && ret->init())
{
ret->autorelease(); // 将该类放入自动释放池。
}
else
{
CC_SAFE_DELETE(ret); // 初始化失败,安全的删除该类。
}
return ret;
}
bool EventListenerMouse::init()
{
// 创建一个鼠标事件的回调函数,其中判断具体是哪个鼠标动作而调用对应的回调函数。
auto listener = (Event* event){
auto mouseEvent = static_cast<EventMouse*>(event);
switch (mouseEvent->_mouseEventType)
{
case EventMouse::MouseEventType::MOUSE_DOWN:
if(onMouseDown != nullptr)
onMouseDown(event);
break;
case EventMouse::MouseEventType::MOUSE_UP:
if(onMouseUp != nullptr)
onMouseUp(event);
break;
case EventMouse::MouseEventType::MOUSE_MOVE:
if(onMouseMove != nullptr)
onMouseMove(event);
break;
case EventMouse::MouseEventType::MOUSE_SCROLL:
if(onMouseScroll != nullptr)
onMouseScroll(event);
break;
default:
break;
}
};
if (EventListener::init(Type::MOUSE, LISTENER_ID, listener)) // 调用父类的初始化函数。
{
return true;
}
return false;
}
(2) virtual EventListenerMouse* clone() override;
克隆一个鼠标事件监听器。
实现源码:
EventListenerMouse* EventListenerMouse::clone()
{
auto ret = new (std::nothrow) EventListenerMouse();
if (ret && ret->init())
{
ret->autorelease();
/* 以下是与EventListenerMouse::create()的区别。
* 因为被克隆的鼠标事件监听器可能已经指定了具体的回调函数,所以这里需要赋值。
*/
ret->onMouseUp = onMouseUp;
ret->onMouseDown = onMouseDown;
ret->onMouseMove = onMouseMove;
ret->onMouseScroll = onMouseScroll;
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
(3) virtual bool checkAvailable() override;
返回鼠标事件监听器是否可用。
实现源码:
bool EventListenerMouse::checkAvailable()
{
/* 没有什么限制,被创建后即可用。
* 这里我觉得应该像EventListenerKeyboard一样判断回调函数非空。
* 起码应该判断至少有一个回调函数非空,否则创建了监听器做什么用。
*/
return true;
}
EventKeyboard:
一、成员变量:
public:
enum class KeyCode // 键盘事件类型,包含各种各样的键盘按键。
{
KEY_NONE, // 未知类型。
KEY_PAUSE,
KEY_SCROLL_LOCK,
KEY_PRINT,
KEY_SYSREQ,
KEY_BREAK,
KEY_ESCAPE,
KEY_BACK = KEY_ESCAPE,
KEY_BACKSPACE,
KEY_TAB,
KEY_BACK_TAB,
KEY_RETURN,
KEY_CAPS_LOCK,
KEY_SHIFT,
KEY_LEFT_SHIFT = KEY_SHIFT,
KEY_RIGHT_SHIFT,
KEY_CTRL,
KEY_LEFT_CTRL = KEY_CTRL,
KEY_RIGHT_CTRL,
KEY_ALT,
KEY_LEFT_ALT = KEY_ALT,
KEY_RIGHT_ALT,
KEY_MENU,
KEY_HYPER,
KEY_INSERT,
KEY_HOME,
KEY_PG_UP,
KEY_DELETE,
KEY_END,
KEY_PG_DOWN,
KEY_LEFT_ARROW,
KEY_RIGHT_ARROW,
KEY_UP_ARROW,
KEY_DOWN_ARROW,
KEY_NUM_LOCK,
KEY_KP_PLUS,
KEY_KP_MINUS,
KEY_KP_MULTIPLY,
KEY_KP_DIVIDE,
KEY_KP_ENTER,
KEY_KP_HOME,
KEY_KP_UP,
KEY_KP_PG_UP,
KEY_KP_LEFT,
KEY_KP_FIVE,
KEY_KP_RIGHT,
KEY_KP_END,
KEY_KP_DOWN,
KEY_KP_PG_DOWN,
KEY_KP_INSERT,
KEY_KP_DELETE,
KEY_F1,
KEY_F2,
KEY_F3,
KEY_F4,
KEY_F5,
KEY_F6,
KEY_F7,
KEY_F8,
KEY_F9,
KEY_F10,
KEY_F11,
KEY_F12,
KEY_SPACE,
KEY_EXCLAM,
KEY_QUOTE,
KEY_NUMBER,
KEY_DOLLAR,
KEY_PERCENT,
KEY_CIRCUMFLEX,
KEY_AMPERSAND,
KEY_APOSTROPHE,
KEY_LEFT_PARENTHESIS,
KEY_RIGHT_PARENTHESIS,
KEY_ASTERISK,
KEY_PLUS,
KEY_COMMA,
KEY_MINUS,
KEY_PERIOD,
KEY_SLASH,
KEY_0,
KEY_1,
KEY_2,
KEY_3,
KEY_4,
KEY_5,
KEY_6,
KEY_7,
KEY_8,
KEY_9,
KEY_COLON,
KEY_SEMICOLON,
KEY_LESS_THAN,
KEY_EQUAL,
KEY_GREATER_THAN,
KEY_QUESTION,
KEY_AT,
KEY_CAPITAL_A,
KEY_CAPITAL_B,
KEY_CAPITAL_C,
KEY_CAPITAL_D,
KEY_CAPITAL_E,
KEY_CAPITAL_F,
KEY_CAPITAL_G,
KEY_CAPITAL_H,
KEY_CAPITAL_I,
KEY_CAPITAL_J,
KEY_CAPITAL_K,
KEY_CAPITAL_L,
KEY_CAPITAL_M,
KEY_CAPITAL_N,
KEY_CAPITAL_O,
KEY_CAPITAL_P,
KEY_CAPITAL_Q,
KEY_CAPITAL_R,
KEY_CAPITAL_S,
KEY_CAPITAL_T,
KEY_CAPITAL_U,
KEY_CAPITAL_V,
KEY_CAPITAL_W,
KEY_CAPITAL_X,
KEY_CAPITAL_Y,
KEY_CAPITAL_Z,
KEY_LEFT_BRACKET,
KEY_BACK_SLASH,
KEY_RIGHT_BRACKET,
KEY_UNDERSCORE,
KEY_GRAVE,
KEY_A,
KEY_B,
KEY_C,
KEY_D,
KEY_E,
KEY_F,
KEY_G,
KEY_H,
KEY_I,
KEY_J,
KEY_K,
KEY_L,
KEY_M,
KEY_N,
KEY_O,
KEY_P,
KEY_Q,
KEY_R,
KEY_S,
KEY_T,
KEY_U,
KEY_V,
KEY_W,
KEY_X,
KEY_Y,
KEY_Z,
KEY_LEFT_BRACE,
KEY_BAR,
KEY_RIGHT_BRACE,
KEY_TILDE,
KEY_EURO,
KEY_POUND,
KEY_YEN,
KEY_MIDDLE_DOT,
KEY_SEARCH,
KEY_DPAD_LEFT,
KEY_DPAD_RIGHT,
KEY_DPAD_UP,
KEY_DPAD_DOWN,
KEY_DPAD_CENTER,
KEY_ENTER,
KEY_PLAY
};
private:
KeyCode _keyCode; // 键盘事件类型。
bool _isPressed; // 键盘的按键是否被按下。
friend class EventListenerKeyboard;
关键点总结:
1、鼠标事件的事件类型是按照动作划分的,其并不区分具体是哪个按键的动作。而键盘事件的事件类型是按具体每个按键动作划分的。
EventListenerKeyboard:
EventListenerKeyboard与EventListenerMouse很相似,与其不同的只有checkAvailable()。
bool EventListenerKeyboard::checkAvailable()
{
if (onKeyPressed == nullptr && onKeyReleased == nullptr) // 判断回调函数非空。回调函数不指定,创建监听器做什么用。
{
CCASSERT(false, "Invalid EventListenerKeyboard!");
return false;
}
return true;
}
EventAcceleration:
一、成员变量:
private:
Acceleration _acc; // 存储重力加速过程中的信息。
friend class EventListenerAcceleration;
其中class Acceleration如下:
class CC_DLL Acceleration
: public Ref
{
public:
double x; // 重力加速过程中X轴坐标。
double y; // 重力加速过程中Y轴坐标。
double z; // 重力加速过程中Z轴坐标。
double timestamp; // 重力加速过程中的时间戳。
Acceleration(): x(0), y(0), z(0), timestamp(0) {}
}
EventListenerAcceleration:
EventListenerAcceleration与EventListenerKeyboard很相似,与其不同的地方如下:
1、监听器在使用EventListenerAcceleration::create()创建时就要给出回调函数,不能像EventListenerKeyboard那样创建监听器与定义回调函数可以单独进行。
2、EventListenerAcceleration的事件回调函数是private方法,这也就说明回调函数被创建后不能在程序中更改。
EventController:
一、成员变量:
public:
enum class ControllerEventType // 游戏控制器(比如游戏手柄)事件类型。
{
CONNECTION, // 游戏控制器连接状态改变。
BUTTON_STATUS_CHANGED, // 游戏控制器按键状态改变。
AXIS_STATUS_CHANGED, // 游戏控制器位置改变。
};
protected:
ControllerEventType _controllerEventType; // 游戏控制器(比如游戏手柄)事件类型。
Controller* _controller; // 游戏控制器的信息。
int _keyCode; // 哪个按键触发了事件。
bool _isConnected; // 游戏控制器是否已连接。
friend class EventListenerController;
二、成员方法:
成员方法都是一些get/set,就不一一列举了。
EventListenerController:
EventListenerController与EventListenerMouse相似,以下只列出不同部分:
1、EventListenerController没有clone(),但是只是简单的返回nullptr,不给用户个提示,这样看起来不友好。
EventListenerController* EventListenerController::clone()
{
return nullptr;
}
2、EventListenerController比EventListenerMouse对事件的分类更加抽象。
例如EventListenerMouse将事件分为鼠标按下,鼠标抬起等等。而EventListenerController对于按键的按下,抬起,按住都归类为BUTTON_STATUS_CHANGED事件。该事件分发到监听器后,由监听器判断具体是按下、抬起或是按住。
bool EventListenerController::init()
{
auto listener = (Event* event){
auto evtController = static_cast<EventController*>(event);
switch (evtController->getControllerEventType())
{
case EventController::ControllerEventType::CONNECTION: // 游戏控制器连接状态改变事件。
if (evtController->isConnected())
{
if (this->onConnected) // 连接。
this->onConnected(evtController->getController(), event);
}
else
{
if (this->onDisconnected) // 断开连接。
this->onDisconnected(evtController->getController(), event);
}
break;
case EventController::ControllerEventType::BUTTON_STATUS_CHANGED: // 游戏控制器按键状态改变事件。
{
const auto& keyStatus = evtController->_controller->_allKeyStatus; // 获得该按键的当前状态(按下或抬起)。
const auto& keyPrevStatus = evtController->_controller->_allKeyPrevStatus; // 获得该按键的之前的状态(按下或抬起)。
if (this->onKeyDown && keyStatus.isPressed && !keyPrevStatus.isPressed) // 如果该按键之前是抬起状态,而现在是按下状态,说明该按键被按下。
{
this->onKeyDown(evtController->_controller, evtController->_keyCode, event);
}
else if (this->onKeyUp && !keyStatus.isPressed && keyPrevStatus.isPressed) // 如果该按键之前是按下状态,而现在是抬起状态,说明该按键被抬起。
{
this->onKeyUp(evtController->_controller, evtController->_keyCode, event);
}
else if (this->onKeyRepeat && keyStatus.isPressed && keyPrevStatus.isPressed) // 如果该按键之前是按下状态,而现在也是按下状态,说明该按键被按住。
{
this->onKeyRepeat(evtController->_controller, evtController->_keyCode, event);
}
}
break;
case EventController::ControllerEventType::AXIS_STATUS_CHANGED: // 游戏控制器位置改变事件。
{
if (this->onAxisEvent)
{
this->onAxisEvent(evtController->_controller, evtController->_keyCode, event);
}
}
break;
default:
CCASSERT(false, "Invalid EventController type");
break;
}
};
if (EventListener::init(EventListener::Type::GAME_CONTROLLER, LISTENER_ID, listener))
{
return true;
}
return false;
}
关键点总结:
1、在调用回调函数的时候,为什么要传递那么多参数?_controller,_keyCode都是可以通过event获得,觉得只传event比较好。
EventFocus:
一、成员变量:
private:
ui::Widget *_widgetGetFocus; // 哪个Widget得到了焦点。
ui::Widget *_widgetLoseFocus; // 哪个Widget失去了焦点。
friend class EventListenerFocus;
EventListenerFocus:
EventListenerFocus与EventListenerKeyboard是一个模式,在这里就不一一列举了。
EventTouch:
一、成员变量:
public:
enum class EventCode // 触摸事件类型。
{
BEGAN, // 触摸开始。
MOVED, // 触摸移动。
ENDED, // 触摸结束。
CANCELLED // 触摸被取消。
};
static const int MAX_TOUCHES = 15; // 最多同时处理的触摸点(单点触摸,多点触摸)。
private:
EventCode _eventCode; // 触摸事件类型。
std::vector<Touch*> _touches; // 同时触摸的点都存放在这个数组中(单点触摸,多点触摸)。
friend class GLView;
二、成员方法:
成员方法都是一些get/set,就不一一列举了。
EventListenerTouchOneByOne和EventListenerTouchAllAtOnce:
EventListenerTouchOneByOne与EventListenerMouse相似,区别在于成员变量多了bool _needSwallow和std::vector<Touch*> _claimedTouches。
_needSwallow的意义在于,在单点触摸的事件中,显示在最前面的节点首先接收到事件。该变量被设置为true时,事件不再被显示在最前面的节点向下传递,反之向下传递。
_claimedTouches的意义在于,一个完整的触摸事件至少分为两个阶段,(BEGAN, ENDED)、(BEGAN, MOVED, ENDED)、(BEGAN, CANCELLED)等等,有BEGAN才有之后的动作。
而_claimedTouches就用于存储成功处理完成的BEGAN事件的触摸点信息,之后的其他事件(MOVED, ENDED, CANCELLED)会在_claimedTouches中寻找自己所对应的BEGAN。
EventListenerTouchAllAtOnce与EventListenerTouchOneByOne相似。
EventCustom:
一、成员变量:
protected:
void* _userData; // 用户数据。
std::string _eventName; // 用户指定的事件名称。
二、成员方法:
都是一些get/set方法。
EventListenerCustom:
EventListenerCustom与EventListenerAcceleration相似,不同之处如下:
1、EventListenerCustom在调用create()创建时需要指定事件监听器的ID(用户起的ID,字符串)。
2、
EventListenerCustom::checkAvailable()还需要调用父类EventListener::checkAvailable(),这个目前不清楚是何用意。
bool EventListenerCustom::checkAvailable()
{
bool ret = false;
if (EventListener::checkAvailable() && _onCustomEvent != nullptr)
{
ret = true;
}
return ret;
}
Event:
父类记录事件的通用信息,子类记录具体事件的特有信息。
一、成员变量:
public:
enum class Type // 事件类型。
{
TOUCH, // 触摸事件。
KEYBOARD, // 键盘事件。
ACCELERATION, // 重力加速事件。
MOUSE, // 鼠标事件。
FOCUS, // 焦点事件。
GAME_CONTROLLER, // 游戏控制器事件。
CUSTOM // 用户自定义事件。
};
protected:
Type _type; // 事件类型。
bool _isStopped; // 事件是否已停止。
Node* _currentTarget; // 触发事件的节点。
friend class EventDispatcher;
EventListener:
一、成员变量:
public:
enum class Type // 事件监听器类型。
{
UNKNOWN, // 未知事件监听器。
TOUCH_ONE_BY_ONE, // 单点触摸事件监听器。
TOUCH_ALL_AT_ONCE, // 多点触摸事件监听器。
KEYBOARD, // 键盘事件监听器。
MOUSE, // 鼠标事件监听器。
ACCELERATION, // 重力加速事件监听器。
FOCUS, // 焦点事件监听器。
GAME_CONTROLLER, // 游戏控制器事件监听器。
CUSTOM // 用户自定义事件监听器。
};
typedef std::string ListenerID;
protected:
std::function<void(Event*)> _onEvent; // 事件触发时的回调函数。
Type _type; // 事件监听器的类型。
ListenerID _listenerID; // 事件监听器的ID(字符串)。
bool _isRegistered; // 事件监听器是否已注册。
/* 事件的优先级。
* 如果
使用addEventListenerWithSceneGraphPriority()注册监听器,则监听器的优先级为0,
* 代表默认的场景图优先级,
显示在前面的节点(ZOrder越大的节点)优先级更高;
* 如果
使用addEventListenerWithFixedPriority()注册监听器,则监听器的优先级为指定的值(非0),
*
数值越小,优先级越高。
/
int _fixedPriority;
Node _node; // 与监听器绑定的节点(触发事件的节点)。
bool _paused; // 事件监听器是否为暂停状态。
bool _isEnabled; // 事件监听器是否为可用状态。
friend class EventDispatcher;
二、成员方法:
get/set方法不在这里一一列举。
(1) bool init(Type t, const ListenerID& listenerID, const std::function<void(Event*)>& callback);
初始化事件监听器的通用信息。
实现源码:
bool EventListener::init(Type t, const ListenerID& listenerID, const std::function<void(Event*)>& callback)
{
_onEvent = callback; // 事件触发时的回调函数。该函数由具体的事件监听器类定义(比如EventListenerMouse)。
_type = t; // 事件监听器的类型。每个事件监听器有自己的类型(比如Event::TYPE::MOUSE)。
_listenerID = listenerID; // 事件监听器的ID。每个事件监听器有自己的ID(比如"__cc_mouse")。
_isRegistered = false; // 事件监听器还未注册。
_paused = true; // 刚创建的事件监听器处于暂停状态。
_isEnabled = true; // 刚创建的事件监听器处于可用状态。
return true;
}