事件系统
cocos2d-x 3.x 的事件比 2.x 要好用很多,也比较简单,主要由三部分组成:事件分发器 EventDispatcher、事件监听器 EventListener 和事件 Event。
cocos2d-x 3.x 所有与事件相关的文件都在 cocos/base 模块下。
事件 Event
事件在现实中的意思是发生了一件事,当事情发生之后会有人去做一些处理;在应用程序中事件可以看作是一个带有数据的实体,而事件监听器就是事情发生之后做一些处理工作的人;派发事件就是将事件这个实体派发出去,告诉大家发生了这个事情,然后事件监听器会监听到事件触发,接收事件这个实体,取到事件中的数据,做相应的处理。
在这里事件可以简单地看成一个数据体,它在整个事件系统中的作用是传送数据,事件派发器将事件派发出去,事件监听器接收到这个数据,然后把事件中的数据取出来,进行下一步操作。事件的基类是 Event,它的定义很简单,只定义了几个属性
class CC_DLL Event : public Ref
{
public:
/** Type Event type.*/
enum class Type
{
TOUCH,
KEYBOARD,
ACCELERATION,
MOUSE,
FOCUS,
GAME_CONTROLLER,
CUSTOM
};
public:
//function
protected:
Type _type; ///< Event type
bool _isStopped; ///< whether the event has been stopped.
Node* _currentTarget; ///< Current target
friend class EventDispatcher;
};
Event 类只声明了事件类型 _type,事件是否停止 _isStopped,事件附加的目标结点 _currentTarget 这三个属性。其中,事件类型共有七种,分别是 触摸事件、键盘事件、加速计事件、鼠标事件、焦点事件、手柄事件和自定义事件。具体事件类在 Event 的基础上多定义了几个属性而已,比如键盘事件 EventKeyboard 就多定义了两个属性,_keyCode 表示按下的键值,_isPressed 表示是按下还是松开
class CC_DLL EventKeyboard : public Event
{
public:
//function
protected:
KeyCode _keyCode;
bool _isPressed;
friend class EventListenerKeyboard;
};
事件监听器 EventListener
事件监听器作为处理事件的“人“,负责监听事件的发生,并调用相应的回调函数来处理事件。
class CC_DLL EventListener : public Ref
{
public:
/** Type Event type.*/
enum class Type
{
UNKNOWN,
TOUCH_ONE_BY_ONE,
TOUCH_ALL_AT_ONCE,
KEYBOARD,
MOUSE,
ACCELERATION,
FOCUS,
GAME_CONTROLLER,
CUSTOM
};
typedef std::string ListenerID;
public:
bool init(Type t, const ListenerID& listenerID, const std::function<void(Event*)>& callback);
virtual bool checkAvailable() = 0;
//...
protected:
std::function<void(Event*)> _onEvent; /// Event callback function
Type _type; /// Event listener type
ListenerID _listenerID; /// Event listener ID
bool _isRegistered; /// Whether the listener has been added to dispatcher.
int _fixedPriority; // The higher the number, the higher the priority, 0 is for scene graph base priority.
Node* _node; // scene graph based priority
bool _paused; // Whether the listener is paused
bool _isEnabled; // Whether the listener is enabled
friend class EventDispatcher;
};
- _fixedPriority 表示事件的优先级,_node 表示事件依赖的场景结点,这两个属性是对立的,分别对应两种添加事件监听器的方式,一种是事件监听器依赖于场景结点,则需要保存这个结点(_node),这时事件不需要额外定义优先级;另一种是事件监听器注册在全局中,则需要定义事件的优先级(_fixedPriority)
- _paused 表示当前监听器是否被暂停,_isEnabled 表示监听器是否启用,_isRegistered 表示监听器是否已注册,每个监听器只能注册一次,如果要重复注册,必须使用 clone 方法创建一个新的副本
- _type 表示事件监听器的类型,这个类型和事件的类型要区分开,事件监听器有自己的一个类型,事件也有自己的类型,这两个类型有对应关系,但它们是两个不同的数据;_listenerID 表示监听器的唯一 ID,这是个字符串,在保存监听器到 map 时用作 key 值,这个 ID 在具体的事件监听器子类中定义
- _onEvent 是事件回调函数,当事件触发时,这个函数将被调用
EventListener 也比较简单,比较重要的两个函数是 init 方法和 checkAvaliable 方法
bool EventListener::init(Type t, const ListenerID& listenerID, const std::function<void(Event*)>& callback)
{
_onEvent = callback;
_type = t;
_listenerID = listenerID;
_isRegistered = false;
_paused = true;
_isEnabled = true;
return true;
}
bool EventListener::checkAvailable()
{
return (_onEvent != nullptr);
}
init 方法初始化几个属性,checkAvaliable 方法检查回调函数是否为空,如果为空,则不能注册监听器,因为没有回调函数,监听器即使监听到事件发生也无法处理事件。这两个方法在基类没看到调用的地方,init 方法在子类初始化的时候会调用,而 checkAvaliable 方法则在注册事件监听器的时候调用。
下面还是以键盘事件为例,看一下具体的事件监听器类做了哪些事件
class CC_DLL EventListenerKeyboard : public EventListener
{
public:
static const std::string LISTENER_ID;
/** Create a keyboard event listener.
*
* @return An autoreleased EventListenerKeyboard object.
*/
static EventListenerKeyboard* create();
/// Overrides
virtual EventListenerKeyboard* clone() override;
virtual bool checkAvailable() override;
std::function<void(EventKeyboard::KeyCode, Event*)> onKeyPressed;
std::function<void(EventKeyboard::KeyCode, Event*)> onKeyReleased;
CC_CONSTRUCTOR_ACCESS:
EventListenerKeyboard();
bool init();
};
EventListenerKeyboard 新定义了两个子回调事件 onKeyPressed 和 onKeyReleased,这个函数参数是对外公开的,由外部进行赋值;这两个子回调函数将由父类定义的回调函数 _onEvent 调用(看下面的 init 方法)。
EventListenerKeyKeyboard 的 init 方法首先会定义回调函数,然后调用父类的 init 方法,将事件类型、事件 ID 和回调函数传过去,做父类属性初始化。
bool EventListenerKeyboard::init()
{
auto listener = [this](Event* event){
auto keyboardEvent = static_cast<EventKeyboard*>(event);
if (keyboardEvent->_isPressed)
{
if (onKeyPressed != nullptr)
onKeyPressed(keyboardEvent->_keyCode, event);
}
else
{
if (onKeyReleased != nullptr)
onKeyReleased(keyboardEvent->_keyCode, event);
}
};
if (EventListener::init(Type::KEYBOARD, LISTENER_ID, listener))
{
return true;
}
return false;
}
init 方法定义了回调函数 _onEvent,_onEvent 会调用 onKeyPressed 或者 onKeyReleased 方法,所以 checkAvailable 方法不用再检查 _onEvent 是否为空,而是检查这两个子回调函数
bool EventListenerKeyboard::checkAvailable()
{
if (onKeyPressed == nullptr && onKeyReleased == nullptr)
{
CCASSERT(false, "Invalid EventListenerKeyboard!");
return false;
}
return true;
}