【Cocos2d-x 3.x】 事件处理机制源码分析
在游戏中,触摸是最基本的,必不可少的。Cocos2d-x 3.x中定义了一系列事件,同时也定义了负责监听这些事件的监听器,另外,cocos定义了事件分发类,用来将事件派发出去以便可以实现相应的事件。
触摸事件
Event
Cocos2d-x 3.x定义了事件基类Event,基于Event,引擎派生出几种事件:
enum class Type
{
TOUCH, // 触摸事件
KEYBOARD, // 键盘事件
ACCELERATION, // 加速度事件
MOUSE,// 鼠标事件
FOCUS,// 焦点事件
CUSTOM // 自定义事件
};
EventTouch
EventTouch是触摸事件中非常重要的一类事件,它定义了四种touch操作:
enum class EventCode
{
BEGAN,
MOVED,
ENDED,
CANCELLED
};
它还定义了一个 static int值,表示最大的触摸点数为15点:
static const int MAX_TOUCHES = 15;
事件监听器
EventListener
EventListener是事件监听器的基类,派生出的监听器对应各种触摸事件:
enum class Type
{
UNKNOWN,
TOUCH_ONE_BY_ONE,
TOUCH_ALL_AT_ONCE,
KEYBOARD,
MOUSE,
ACCELERATION,
FOCUS,
CUSTOM
};
另外:
1.一个Listener想接收事件必须是enabled true 并且 paused false。
2.值得注意的是,pause的变量专门是为了scenGraph类的事件存在的(后续有说明),而且一个Node的onEnter和onExit 事件会影响到与Node相关的该类事件的pause状态。
EventListenerTouchOneByOne
单点触摸方式,实现它时需要重写父类的四种触摸方式的函数:
/// 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;
另外, 单点触摸当onTouchBegan函数不是nullptr时,它就是可用的:
bool EventListenerTouchOneByOne::checkAvailable()
{
// EventDispatcher will use the return value of 'onTouchBegan' to determine whether to pass following 'move', 'end'
// message to 'EventListenerTouchOneByOne' or not. So 'onTouchBegan' needs to be set.
if (onTouchBegan == nullptr)
{
CCASSERT(false, "Invalid EventListenerTouchOneByOne!");
return false;
} return true;
}
还有,EventListenerTouchOneByOne可以设置吞噬。
EventListenerAllAtOnce
多点触摸,当四种触摸方式函数都不为nullptr时,EventListenerAllAtOnce时可用的:
bool EventListenerTouchAllAtOnce::checkAvailable()
{
if (onTouchesBegan == nullptr && onTouchesMoved == nullptr
&& onTouchesEnded == nullptr && onTouchesCancelled == nullptr)
{
CCASSERT(false, "Invalid EventListenerTouchAllAtOnce!");
return false;
} return true;
}
EventListenerCustom
EventListenerID是根据独特的Name进行命名的,值得注意的是请确保你的name是具有唯一性的,否在在DispatchCustomEvent时会有问题。
事件分发器EventDispatcher
首先,先看一个内嵌类EventListenerVector:
class EventListenerVector
{
public:
EventListenerVector();
~EventListenerVector();
size_t size() const;
bool empty() const; void push_back(EventListener* item);
void clearSceneGraphListeners();
void clearFixedListeners();
void clear(); inline std::vector<EventListener*>* getFixedPriorityListeners() const { return _fixedListeners; };
inline std::vector<EventListener*>* getSceneGraphPriorityListeners() const { return _sceneGraphListeners; };
inline ssize_t getGt0Index() const { return _gt0Index; };
inline void setGt0Index(ssize_t index) { _gt0Index = index; };
private:
std::vector<EventListener*>* _fixedListeners;
std::vector<EventListener*>* _sceneGraphListeners;
ssize_t _gt0Index;
};
在EventDispatcher的成员变量中有一个map :std::unordered_map<EventListener::ListenerID, EventListenerVector*> _listenerMap; 一种ListenerID对应了一个Vector。
EventDispatcher有三种添加事件的方式:addEventListenerWithSceneGraphPriority、addEventListenerWithFixedPriority和addCustomEventListener
void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node)
{
CCASSERT(listener && node, "Invalid parameters.");
CCASSERT(!listener->isRegistered(), "The listener has been registered."); //检查Listener可用性
if (!listener->checkAvailable())
return;
//设置listener相关属性
listener->setAssociatedNode(node);
listener->setFixedPriority(0);
listener->setRegistered(true); addEventListener(listener);
}
addEventListenerWithSceneGraphPriority将监听器和node节点关联起来,addEventListenerWithSceneGraphPriority不需要手动移除监听器,因为在node的析构函数中会自动移除的,还有,addEventListenerWithSceneGraphPriority设置监听器的优先权为0。优先权值越小,越先派发。
void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority)
{
CCASSERT(listener, "Invalid parameters.");
//一个事件只能被注册一次
CCASSERT(!listener->isRegistered(), "The listener has been registered.");
//Fixed类型的事件优先级不能是0
CCASSERT(fixedPriority != 0, "0 priority is forbidden for fixed priority since it's used for scene graph based priority."); //检查可用性
if (!listener->checkAvailable())
return; //设置关联属性
listener->setAssociatedNode(nullptr);
listener->setFixedPriority(fixedPriority);
listener->setRegistered(true);
listener->setPaused(false); addEventListener(listener);
}
addEventListenerWithFixedPriority不能将监听器的优先权设置为0。
EventListenerCustom* EventDispatcher::addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback)
{
//custom类的事件添加是通过eventName 和 eventcallBack来进行添加的
EventListenerCustom *listener = EventListenerCustom::create(eventName, callback); //custom的事件优先级被默认为1
addEventListenerWithFixedPriority(listener, 1); return listener;
}
addCustomEventListener通过eventName和eventcallBack来添加的,自定以监听器优先权默认为1.。
这三种方法都使用了addEventListener函数来进行实际操作:
void EventDispatcher::addEventListener(EventListener* listener)
{
//如果当前Dispatcher正在进行事件Dispatch,则放到toAddList中。
if (_inDispatch == 0)
{
forceAddEventListener(listener);
}
else
{
// std::vector
_toAddedListeners.push_back(listener);
} listener->retain();
}
如果当前派发器没有进行事件的派发,则强制进行,并添加该事件监听器,该操作由forceAddEventListener来完成:
void EventDispatcher::forceAddEventListener(EventListener* listener)
{
EventListenerVector* listeners = nullptr;
EventListener::ListenerID listenerID = listener->getListenerID(); //找到该类eventlistener的vector,此处的vector是EventVector
auto itr = _listenerMap.find(listenerID);
//如果没有找到,则需要向map中添加一个pair
if (itr == _listenerMap.end())
{ listeners = new EventListenerVector();
_listenerMap.insert(std::make_pair(listenerID, listeners));
}
else
{
listeners = itr->second;
}
//将该类别listenerpush_back进去(这个函数调用的是EventVector的pushback哦)
listeners->push_back(listener); //如果优先级是0,则设置为graph。
if (listener->getFixedPriority() == 0)
{
//设置该listenerID的DirtyFlag
//(setDirty函数可以这样理解,每个ListenerID都有特定的dirtyFlag,每次进行add操作后,都要更新该ID的flag)
setDirty(listenerID, DirtyFlag::SCENE_GRAPH_PRIORITY); //如果是sceneGraph类的事件,则需要处理两个方面:
//1.将node 与event 关联
//2.如果该node是运行中的,则需要恢复其事件(因为默认的sceneGraph listener的状态时pause)
//增加该listener与node的关联
auto node = listener->getAssociatedNode();
CCASSERT(node != nullptr, "Invalid scene graph priority!"); associateNodeAndEventListener(node, listener); //恢复node的运行状态
if (node->isRunning())
{
resumeEventListenersForTarget(node);
}
}
else
{
setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
}
}
如果优先级是0,则还有将监听器与node关联起来的操作:
void EventDispatcher::associateNodeAndEventListener(Node* node, EventListener* listener)
{
//将listener与node关联,先从map中找到与该node相关的listener vector
std::vector<EventListener*>* listeners = nullptr;
auto found = _nodeListenersMap.find(node);
if (found != _nodeListenersMap.end())
{
listeners = found->second;
}
else
{
listeners = new std::vector<EventListener*>();
_nodeListenersMap.insert(std::make_pair(node, listeners));
}
//vector内添加该listener,这里的vector 是std::vector
listeners->push_back(listener);
}
DispatchEvent(核心)
touch事件和其他事件的分发是不同的:
void EventDispatcher::dispatchEvent(Event* event)
{
if (!_isEnabled)
return; //为dirtyNodesVector中的dirtyNode更新Scene Flag。
updateDirtyFlagForSceneGraph(); DispatchGuard guard(_inDispatch); //特殊touch事件,转到特殊的touch事件处理
if (event->getType() == Event::Type::TOUCH)
{
dispatchTouchEvent(static_cast<EventTouch*>(event));
return;
} //根据事件的类型,获取事件的ID
auto listenerID = __getListenerID(event); //根据事件ID,将该类事件进行排序(先响应谁)
sortEventListeners(listenerID); auto iter = _listenerMap.find(listenerID);
if (iter != _listenerMap.end())
{
auto listeners = iter->second;
//该类事件的lambda函数
auto onEvent = [&event](EventListener* listener) -> bool{
//设置event的target
event->setCurrentTarget(listener->getAssociatedNode());
//调用响应函数
listener->_onEvent(event);
//返回是否已经停止
return event->isStopped();
}; //将该类事件的listeners 和 该类事件的 lambda函数传给该函数
dispatchEventToListeners(listeners, onEvent);
} //更新该事件相关的listener
updateListeners(event);
}
先看看touch事件的分发机制:
void EventDispatcher::dispatchTouchEvent(EventTouch* event)
{
//先将EventListeners排序
sortEventListeners(EventListenerTouchOneByOne::LISTENER_ID);
sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID); auto oneByOneListeners = getListeners(EventListenerTouchOneByOne::LISTENER_ID);
auto allAtOnceListeners = getListeners(EventListenerTouchAllAtOnce::LISTENER_ID); // If there aren't any touch listeners, return directly.
if (nullptr == oneByOneListeners && nullptr == allAtOnceListeners)
return; //mutableTouches是用来处理allAtOnce的
bool isNeedsMutableSet = (oneByOneListeners && allAtOnceListeners); //这些touch都来自该事件
const std::vector<Touch*>& originalTouches = event->getTouches();
std::vector<Touch*> mutableTouches(originalTouches.size());
std::copy(originalTouches.begin(), originalTouches.end(), mutableTouches.begin()); //
// process the target handlers 1st
//
if (oneByOneListeners)
{
auto mutableTouchesIter = mutableTouches.begin();
auto touchesIter = originalTouches.begin();
//遍历touches,每一个touch都来自于同一个事件
for (; touchesIter != originalTouches.end(); ++touchesIter)
{
bool isSwallowed = false; //事件处理的lambda函数
auto onTouchEvent = [&](EventListener* l) -> bool { // Return true to break
EventListenerTouchOneByOne* listener = static_cast<EventListenerTouchOneByOne*>(l); // Skip if the listener was removed.
if (!listener->_isRegistered)
return false; event->setCurrentTarget(listener->_node);
//claimed代表该listener是否接收了该touch(Began返回true or false)
bool isClaimed = false;
std::vector<Touch*>::iterator removedIter; //根据eventNode的不同,会调用不同的callBack函数
EventTouch::EventCode eventCode = event->getEventCode(); if (eventCode == EventTouch::EventCode::BEGAN)
{
//调用began
if (listener->onTouchBegan)
{
isClaimed = listener->onTouchBegan(*touchesIter, event);
if (isClaimed && listener->_isRegistered)
{
//返回true后 将该touch放入该listener的claimedTouches
listener->_claimedTouches.push_back(*touchesIter);
}
}
}
//如果是后三个move end cancel
else if (listener->_claimedTouches.size() > 0
&& ((removedIter = std::find(listener->_claimedTouches.begin(), listener->_claimedTouches.end(), *touchesIter)) != listener->_claimedTouches.end()))
{
isClaimed = true;
//调用相应的callBack
switch (eventCode)
{
case EventTouch::EventCode::MOVED:
if (listener->onTouchMoved)
{
listener->onTouchMoved(*touchesIter, event);
}
break;
case EventTouch::EventCode::ENDED:
if (listener->onTouchEnded)
{
listener->onTouchEnded(*touchesIter, event);
}
if (listener->_isRegistered)
{
listener->_claimedTouches.erase(removedIter);
}
break;
case EventTouch::EventCode::CANCELLED:
if (listener->onTouchCancelled)
{
listener->onTouchCancelled(*touchesIter, event);
}
if (listener->_isRegistered)
{
listener->_claimedTouches.erase(removedIter);
}
break;
default:
CCASSERT(false, "The eventcode is invalid.");
break;
}
} // If the event was stopped, return directly.
if (event->isStopped())
{
updateListeners(event);
return true;
} CCASSERT((*touchesIter)->getID() == (*mutableTouchesIter)->getID(), ""); //如果接收该touch并且需要吞噬该touch,会有两个影响
//1.Touches(standard 触摸机制)的触摸操作都接收不到该touch了
//2.因为返回值是true,在调用dispatchEventToListeners时,在该node之后的node将会不再接收该touch
if (isClaimed && listener->_isRegistered && listener->_needSwallow)
{
if (isNeedsMutableSet)
{
mutableTouchesIter = mutableTouches.erase(mutableTouchesIter);
isSwallowed = true;
}
return true;
} return false;
}; //结合上面的dispatchEventToListeners的源码分析,可以看出新版本的OneByOne touch机制是这样的:
//1.listener根据Node的优先级排序后,依次响应。值得注意的是,新版本的优先级是根据Node的global Zorder来的,而不是2.x的触摸优先级。
//2.当TouchEvent Began来了之后,所有的listener会依次影响Touch Began。然后再依次响应Touch Move...而不是一个listener响应完
//began move end之后 轮到下一个listener响应的顺序。
//3.吞噬操作只有发生在began return true后才可以发生
dispatchEventToListeners(oneByOneListeners, onTouchEvent);
if (event->isStopped())
{
return;
} if (!isSwallowed)
++mutableTouchesIter;
}
} //
// process standard handlers 2nd
//
//相比于OneByOne,AllAtOnce要简单许多。值得注意的是被吞噬的touch也不会被AllAtOnce响应到
if (allAtOnceListeners && mutableTouches.size() > 0)
{ auto onTouchesEvent = [&](EventListener* l) -> bool{
EventListenerTouchAllAtOnce* listener = static_cast<EventListenerTouchAllAtOnce*>(l);
// Skip if the listener was removed.
if (!listener->_isRegistered)
return false; event->setCurrentTarget(listener->_node); switch (event->getEventCode())
{
case EventTouch::EventCode::BEGAN:
if (listener->onTouchesBegan)
{
listener->onTouchesBegan(mutableTouches, event);
}
break;
case EventTouch::EventCode::MOVED:
if (listener->onTouchesMoved)
{
listener->onTouchesMoved(mutableTouches, event);
}
break;
case EventTouch::EventCode::ENDED:
if (listener->onTouchesEnded)
{
listener->onTouchesEnded(mutableTouches, event);
}
break;
case EventTouch::EventCode::CANCELLED:
if (listener->onTouchesCancelled)
{
listener->onTouchesCancelled(mutableTouches, event);
}
break;
default:
CCASSERT(false, "The eventcode is invalid.");
break;
} // If the event was stopped, return directly.
if (event->isStopped())
{
updateListeners(event);
return true;
} return false;
}; dispatchEventToListeners(allAtOnceListeners, onTouchesEvent);
if (event->isStopped())
{
return;
}
} updateListeners(event);
}
在第34行代码开始可以看到,如果单点触摸的onTouchBegan函数返回值不是true,那么后面的onTouchMoved、onTouchEnded和onTouchCancelled也就不会触发了。
3.x中的OneByOne机制:
1.listener根据Node的优先级排序后,依次响应。值得注意的是,新版本的优先级是根据Node的global Zorder来的,而不是2.x的触摸优先级。
2.当TouchEvent Began来了之后,所有的listener会依次影响Touch Began。然后再依次响应Touch Move...而不是一个listener响应完 began move end之后 轮到下一个listener响应的顺序。
3.吞噬操作只有发生在began return true后才可以发生
个人总结,肯定会有很多不足之处,希望路过的旁友们指出。。。
【Cocos2d-x 3.x】 事件处理机制源码分析的更多相关文章
- Android事件分发机制源码分析
Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...
- Springboot学习04-默认错误页面加载机制源码分析
Springboot学习04-默认错误页面加载机制源码分析 前沿 希望通过本文的学习,对错误页面的加载机制有这更神的理解 正文 1-Springboot错误页面展示 2-Springboot默认错误处 ...
- ApplicationEvent事件机制源码分析
<spring扩展点之三:Spring 的监听事件 ApplicationListener 和 ApplicationEvent 用法,在spring启动后做些事情> <服务网关zu ...
- Android查缺补漏(View篇)--事件分发机制源码分析
在上一篇博文中分析了事件分发的流程及规则,本篇会从源码的角度更进一步理解事件分发机制的原理,如果对事件分发规则还不太清楚的童鞋,建议先看一下上一篇博文 <Android查缺补漏(View篇)-- ...
- Android异步消息传递机制源码分析
1.Android异步消息传递机制有以下两个方式:(异步消息传递来解决线程通信问题) handler 和 AsyncTask 2.handler官方解释的用途: 1).定时任务:通过handler.p ...
- hadoop的RPC机制 -源码分析
这些天一直奔波于长沙和武汉之间,忙着腾讯的笔试.面试,以至于对hadoop RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上 ...
- 【RabbitMQ学习记录】- 消息队列存储机制源码分析
本文来自 网易云社区 . RabbitMQ在金融系统,OpenStack内部组件通信和通信领域应用广泛,它部署简单,管理界面内容丰富使用十分方便.笔者最近在研究RabbitMQ部署运维和代码架构,本篇 ...
- Hadoop心跳机制源码分析
正文: 一.体系背景 首先和大家说明一下:hadoop的心跳机制的底层是通过RPC机制实现的,这篇文章我只介绍心跳实现的代码,对于底层的具体实现,大家可以参考我的另几篇博客: 1. hadoop的RP ...
- Hadoop的RPC机制源码分析
分析对象: hadoop版本:hadoop 0.20.203.0 必备技术点: 1. 动态代理(参考 :http://www.cnblogs.com/sh425/p/6893662.html )2. ...
随机推荐
- 支付宝APP支付后台参数生成Java版(一)
一.支付参数组装: String[] parameters={ "service=\"mobile.securitypay.pay\"",//固定值 " ...
- Buffer、Channel示例
a.txt 孔雀向西飞,今朝更好看.孔雀向西飞,今朝更好看.孔雀向西飞,今朝更好看.孔雀向西飞,今朝更好看. 示例一. package com.test; import java.io.FileI ...
- java基础之 重排序
重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段.重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. 在并发程序中,程序员会特别关注不同进程 ...
- python 中的高级函数filter()
filter()函数是 Python 内置的另一个有用的高阶函数,filter()函数接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断,返回 True或 False,filt ...
- ie11浏览器和chrome浏览器对于bgsound和background的一些区别
今天在编写一个非常简单的网页的时候,按照书上写的,使用了一个jpg图片作为背景图片,用background属性放在<body>标签内,同时使用<bgsound>标签插入背景音乐 ...
- Unity中创建二维码
在网络上发现了一个可以把字符串转换成二维码的dll,但是我们要怎么使用他呢.不废话,直接进入主题. 用到的引用 using UnityEngine;using ZXing;using ZXing.Qr ...
- 字节序相关问题简单总结,LSB与MSB
细细碎碎的知识点还真是不少啊,今天总结下通信中的数据字节序的问题. 先来认识名词: MSB:Most Significant Bit. “最高有效位” LSB:Least Significant ...
- iShare.js分享插件
iShare.js是一个小巧的分享插件,纯JS编写,不依赖任何第三方库,使用简便. 为啥写这个插件? 因为在搭建个人blog时(还没有搭建好(¯﹃¯)),对目前国内比较受欢迎的分享插件都不太满意,主要 ...
- Python学习路程day20
本节内容: 项目:开发一个简单的BBS论坛 需求: 整体参考“抽屉新热榜” + “虎嗅网” 实现不同论坛版块 帖子列表展示 帖子评论数.点赞数展示 在线用户展示 允许登录用户发贴.评论.点赞 允许上传 ...
- mybatis配置log4j显示sql语句
log4j.properties文件下: log4j.rootLogger=DEBUG, Console #Console log4j.appender.Console=org.apache.log4 ...