cocox2dx 3.2的事件系统是基于观察者模式,又称订阅者模式来实现的。
观察者/订阅者模式
概述
实现方式
参与实现该模式的模型包括触发者、响应者、事件分发器。程序运行中,触发者向事件分发器发送一种类型的状态改变消息,事件分发器获取到状态消息时,检查是否有响应者已经订阅过此消息,如果有,则转发给相应的响应者去处理该消息。如果没有,则不做任何处理。
优缺点
优点:
1,减少了模块之间的耦合度。
2,程序运行中动态的决定是否响应某个事件。
3,代码层次更为清晰,模块独立性更好,有利于单元测试。
缺点:
cocos2dx 3.2的事件机制
EventListener类中有三个开发者需要了解的类型:
- /**
- * The base class of event listener.
- * If you need custom listener which with different callback,you need to inherit this class.
- * For instance,you could refer to EventListenerAcceleration,EventListenerKeyboard,EventListenerTouchOneByOne,EventListenerCustom.
- */
- class EventListener : public Ref
- {
- public:
- enum class Type
- {
- UNKNOWN,TOUCH_ONE_BY_ONE,TOUCH_ALL_AT_ONCE,KEYBOARD,MOUSE,ACCELERATION,FOCUS,#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
- GAME_CONTROLLER,#endif
- CUSTOM
- };
- typedef std::string ListenerID;
- protected:
- /** Constructor */
- EventListener();
- /** Initializes event with type and callback function */
- bool init(Type t,const ListenerID& listenerID,const std::function<void(Event*)>& callback);
Type,即事件类型,是触摸消息还是键盘消息,或者是开发者自定义的消息。只有七种类型,分别是单点触摸,多点触摸,键盘事件,鼠标事件,重力加速度消息,焦点改变消息,以及开发者自定义消息。这里使用的CUSTOM消息,其实是跟2.x版本中的NotificationCenter使用效果是一样的。
listenerID,是为了区分具体的响应者。除了CUSTOM类型以外,其他类型listenerID和type都是唯一一个。CUSTOM类型,listenerID是创建时开发者自己定义的,EventListener接收到带有listnerID的CUSTOM类型消息时,会根据listnerID查找相应的响应者处理,对应下面接口中的eventName。
- EventListenerCustom* EventDispatcher::addCustomEventListener(const std::string &eventName,const std::function<void(EventCustom*)>& callback)
- {
- EventListenerCustom *listener = EventListenerCustom::create(eventName,callback);
- addEventListenerWithFixedPriority(listener,1);
- return listener;
- }
callback,响应者的消息处理函数。
触摸事件的使用方式
3.x版本中的触摸分为两种类型,多点触摸:EventListenerTouchAllAtOnce,单点触摸:EventListenerTouchOneByOne。
EventListenerTouchAllAtOnce
- const std::string EventListenerTouchAllAtOnce::LISTENER_ID = "__cc_touch_all_at_once";
- class EventListenerTouchAllAtOnce : public EventListener
- {
- public:
- static const std::string LISTENER_ID;
- static EventListenerTouchAllAtOnce* create();
- virtual ~EventListenerTouchAllAtOnce();
- /// Overrides
- virtual EventListenerTouchAllAtOnce* clone() override;
- virtual bool checkAvailable() override;
- //
- public:
- 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;
多点触摸继承自EventListener,TYPE为TOUCH_ALL_AT_ONCE,listener_ID为,__cc_touch_all_at_once,从onTouchesBegan方法中可以看出,这里是一次处理了多个触摸点的消息。
EventListenerTouchOneByOne
- const std::string EventListenerTouchOneByOne::LISTENER_ID = "__cc_touch_one_by_one";
单点触摸的实现方式也是继承自EventListener,TYPE为TOUCH_ONE_BY_ONE,listener_ID为__cc_touch_one_by_one,这里是将多个触摸消息按照触发顺序依次响应,例如如果同时在屏幕上按了四个点,则触摸响应函数会触发四次。onTouchBegan是需要注意的一个函数,返回值是bool,程序至少得实现这个函数,否则无法接收到触摸消息。返回值为false的时候,onTouchMoved,onTouchEnded,onTouchCancelled则不再执行,触摸消息直接传递给下一个接收者。2.x版本的触摸当这个函数返回true时表示吞噬触摸消息,不再往下传递。3.x版本需要设置setSwallowTouched为true,则触摸消息不再往下传递。在此事件中需要 处理两个按钮不能同时响应的问题。
- class EventListenerTouchOneByOne : public EventListener
- {
- public:
- static const std::string LISTENER_ID;
- static EventListenerTouchOneByOne* create();
- virtual ~EventListenerTouchOneByOne();
- void setSwallowTouches(bool needSwallow);
- bool isSwallowTouches();
- /// Overrides
- virtual EventListenerTouchOneByOne* clone() override;
- virtual bool checkAvailable() override;
- //
- public:
- std::function<bool(Touch*,Event*)> onTouchBegan;
- std::function<void(Touch*,Event*)> onTouchMoved;
- std::function<void(Touch*,Event*)> onTouchEnded;
- std::function<void(Touch*,Event*)> onTouchCancelled;
触摸的另一个问题是如何控制触摸响应的顺序,触摸响应的顺序根响应的优先级有关。如下是创建触摸的两个通用函数:
第一个接口,是根据node元素的绘制顺序来决定触摸优先级,元素越晚绘制,即元素的zorder越高,则优先级越高。即使动态改变了node元素的层级关系,触摸也能正常响应。接口注释中也写得比较清楚,接口会默认把元素的优先级设置为0。触摸分发时,先分发优先级小于0的,然后再分发等于0的,即是根据元素层级关系来分发,最后分发大于0的。元素移除时,会自动移除相关联的触摸监听者。
- /** Adds a event listener for a specified event with the priority of scene graph.
- * @param listener The listener of a specified event.
- * @param node The priority of the listener is based on the draw order of this node.
- * @note The priority of scene graph will be fixed value 0. So the order of listener item
- * in the vector will be ' <0,scene graph (0 priority),>0'.
- */
- void addEventListenerWithSceneGraPHPriority(EventListener* listener,Node* node);
- /** Adds a event listener for a specified event with the fixed priority.
- * @param listener The listener of a specified event.
- * @param fixedPriority The fixed priority of the listener.
- * @note A lower priority will be called before the ones that have a higher value.
- * 0 priority is forbidden for fixed priority since it's used for scene graph based priority.
- */
- void addEventListenerWithFixedPriority(EventListener* listener,int fixedPriority);
第二个接口,是直接设置了触摸响应的优先级,可以设置除了0之外的优先级。优先级越低,触摸响应越早。即按照优先级从小到大的顺序分发。
触摸使用示例:
- auto touchListener = EventListenerTouchOneByOne::create();
- touchListener->onTouchBegan = CC_CALLBACK_2(RoomLayer::onTouchBegan,this);
- touchListener->onTouchEnded = CC_CALLBACK_2(RoomLayer::onTouchEnd,this);
- touchListener->onTouchCancelled = CC_CALLBACK_2(RoomLayer::onTouchCancle,this);
- touchListener->setSwallowTouches(true);
- _eventDispatcher->addEventListenerWithSceneGraPHPriority(touchListener,btn);
开发者自定义事件使用示例:
- auto toForegroundListener = EventListenerCustom::create(EVENT_COME_TO_FOREGROUND,[this](EventCustom* event)
- {
- onTouchEnded(nullptr,nullptr);
- });
- _eventDispatcher->addEventListenerWithSceneGraPHPriority(toForegroundListener,this);