基本元素:
- 事件监听器( EventListener):负责接收事件,并执行预定义的事件处理函数
- 事件分发器(EventDispatcher):负责发起通知
- 事件对象(Event):记录事件的相关信息
流程图:
首先,非常重要的一点是,游戏的运行是以CCDirector为控制中心来运行的。
在main函数创建一个AppDelegate实例后,调用Application的全局唯一实例的run()方法启动游戏。
#include "../Classes/AppDelegate.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string>
USING_NS_CC;
int main(int argc, char **argv)
{
// create the application instance
AppDelegate app;
return Application::getInstance()->run();
}
int Application::run()
{
PVRFrameEnableControlWindow(false);
///
/// changing timer resolution
///
UINT TARGET_RESOLUTION = 1; // 1 millisecond target resolution
TIMECAPS tc;
UINT wTimerRes = 0;
if (TIMERR_NOERROR == timeGetDevCaps(&tc, sizeof(TIMECAPS)))
{
wTimerRes = std::min(std::max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax);
timeBeginPeriod(wTimerRes);
}
// Main message loop:
LARGE_INTEGER nLast;
LARGE_INTEGER nNow;
QueryPerformanceCounter(&nLast);
initGLContextAttrs();
// Initialize instance and cocos2d.
if (!applicationDidFinishLaunching())
{
return 1;
}
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
// Retain glview to avoid glview being released in the while loop
glview->retain();
LONGLONG interval = 0LL;
LONG waitMS = 0L;
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
while(!glview->windowShouldClose())
{
QueryPerformanceCounter(&nNow);
interval = nNow.QuadPart - nLast.QuadPart;
if (interval >= _animationInterval.QuadPart)
{
nLast.QuadPart = nNow.QuadPart;
director->mainLoop();
glview->pollEvents();
}
else
{
// The precision of timer on Windows is set to highest (1ms) by 'timeBeginPeriod' from above code,
// but it's still not precise enough. For example, if the precision of timer is 1ms,
// Sleep(3) may make a sleep of 2ms or 4ms. Therefore, we subtract 1ms here to make Sleep time shorter.
// If 'waitMS' is equal or less than 1ms, don't sleep and run into next loop to
// boost CPU to next frame accurately.
waitMS = (_animationInterval.QuadPart - interval) * 1000LL / freq.QuadPart - 1L;
if (waitMS > 1L)
Sleep(waitMS);
}
}
// Director should still do a cleanup if the window was closed manually.
if (glview->isOpenGLReady())
{
director->end();
director->mainLoop();
director = nullptr;
}
glview->release();
///
/// restoring timer resolution
///
if (wTimerRes != 0)
{
timeEndPeriod(wTimerRes);
}
return 0;
}
initGLContextAttrs()方法在其子类AppDelefate中实现,作用是设置 OpenGL context 的属性,目前 cocos2d-x 只能设置六个属性,分别是 rgba 四个颜色属性,深度和模板。
// if you want a different context, modify the value of glContextAttrs
// it will affect all platforms
void AppDelegate::initGLContextAttrs()
{
// set OpenGL context attributes: red,green,blue,alpha,depth,stencil,multisamplesCount
GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8, 0};
GLView::setGLContextAttrs(glContextAttrs);
}
applicationDidFinishLaunching()方法同样在AppDelegate中实现,作用是注册OpenGL窗口的事件,并进行监听。
bool AppDelegate::applicationDidFinishLaunching() {
// initialize director
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
glview = GLViewImpl::createWithRect("HelloCpp", cocos2d::Rect(0, 0, designResolutionSize.width, designResolutionSize.height));
#else
glview = GLViewImpl::create("HelloCpp");
#endif
director->setOpenGLView(glview);
}
// turn on display FPS
director->setDisplayStats(true);
// set FPS. the default value is 1.0/60 if you don't call this
director->setAnimationInterval(1.0f / 60);
// Set the design resolution
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
auto frameSize = glview->getFrameSize();
// if the frame's height is larger than the height of medium size.
if (frameSize.height > mediumResolutionSize.height)
{
director->setContentScaleFactor(MIN(largeResolutionSize.height/designResolutionSize.height, largeResolutionSize.width/designResolutionSize.width));
}
// if the frame's height is larger than the height of small size.
else if (frameSize.height > smallResolutionSize.height)
{
director->setContentScaleFactor(MIN(mediumResolutionSize.height/designResolutionSize.height, mediumResolutionSize.width/designResolutionSize.width));
}
// if the frame's height is smaller than the height of medium size.
else
{
director->setContentScaleFactor(MIN(smallResolutionSize.height/designResolutionSize.height, smallResolutionSize.width/designResolutionSize.width));
}
register_all_packages();
// create a scene. it's an autorelease object
auto scene = HelloWorld::createScene();
// run
director->runWithScene(scene);
return true;
}
静态方法GLViewImpl::createWithRect()方法创建了一个窗口,并且完成了事件的注册:
GLViewImpl* GLViewImpl::create(const std::string& viewName)
{
return GLViewImpl::create(viewName, false);
}
GLViewImpl* GLViewImpl::create(const std::string& viewName, bool resizable)
{
auto ret = new (std::nothrow) GLViewImpl;
if(ret && ret->initWithRect(viewName, Rect(0, 0, 960, 640), 1.0f, resizable)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
GLViewImpl* GLViewImpl::createWithRect(const std::string& viewName, Rect rect, float frameZoomFactor, bool resizable)
{
auto ret = new (std::nothrow) GLViewImpl;
if(ret && ret->initWithRect(viewName, rect, frameZoomFactor, resizable)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
GLViewImpl* GLViewImpl::createWithFullScreen(const std::string& viewName)
{
auto ret = new (std::nothrow) GLViewImpl();
if(ret && ret->initWithFullScreen(viewName)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
GLViewImpl* GLViewImpl::createWithFullScreen(const std::string& viewName, const GLFWvidmode &videoMode, GLFWmonitor *monitor)
{
auto ret = new (std::nothrow) GLViewImpl();
if(ret && ret->initWithFullscreen(viewName, videoMode, monitor)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
bool GLViewImpl::initWithRect(const std::string& viewName, Rect rect, float frameZoomFactor, bool resizable)
{
setViewName(viewName);
_frameZoomFactor = frameZoomFactor;
glfwWindowHint(GLFW_RESIZABLE,resizable?GL_TRUE:GL_FALSE);
glfwWindowHint(GLFW_RED_BITS,_glContextAttrs.redBits);
glfwWindowHint(GLFW_GREEN_BITS,_glContextAttrs.greenBits);
glfwWindowHint(GLFW_BLUE_BITS,_glContextAttrs.blueBits);
glfwWindowHint(GLFW_ALPHA_BITS,_glContextAttrs.alphaBits);
glfwWindowHint(GLFW_DEPTH_BITS,_glContextAttrs.depthBits);
glfwWindowHint(GLFW_STENCIL_BITS,_glContextAttrs.stencilBits);
glfwWindowHint(GLFW_SAMPLES, _glContextAttrs.multisamplingCount);
int neededWidth = rect.size.width * _frameZoomFactor;
int neededHeight = rect.size.height * _frameZoomFactor;
_mainWindow = glfwCreateWindow(neededWidth, neededHeight, _viewName.c_str(), _monitor, nullptr);
if (_mainWindow == nullptr)
{
std::string message = "Can't create window";
if (!_glfwError.empty())
{
message.append("\nMore info: \n");
message.append(_glfwError);
}
MessageBox(message.c_str(), "Error launch application");
return false;
}
/*
* Note that the created window and context may differ from what you requested,
* as not all parameters and hints are
* [hard constraints](@ref window_hints_hard). This includes the size of the
* window, especially for full screen windows. To retrieve the actual
* attributes of the created window and context, use queries like @ref
* glfwGetWindowAttrib and @ref glfwGetWindowSize.
*
* see declaration glfwCreateWindow
*/
int realW = 0, realH = 0;
glfwGetWindowSize(_mainWindow, &realW, &realH);
if (realW != neededWidth)
{
rect.size.width = realW / _frameZoomFactor;
}
if (realH != neededHeight)
{
rect.size.height = realH / _frameZoomFactor;
}
glfwMakeContextCurrent(_mainWindow);
glfwSetMouseButtonCallback(_mainWindow, GLFWEventHandler::onGLFWMouseCallBack);
glfwSetCursorPosCallback(_mainWindow, GLFWEventHandler::onGLFWMouseMoveCallBack);
glfwSetScrollCallback(_mainWindow, GLFWEventHandler::onGLFWMouseScrollCallback);
glfwSetCharCallback(_mainWindow, GLFWEventHandler::onGLFWCharCallback);
glfwSetKeyCallback(_mainWindow, GLFWEventHandler::onGLFWKeyCallback);
glfwSetWindowPosCallback(_mainWindow, GLFWEventHandler::onGLFWWindowPosCallback);
glfwSetFramebufferSizeCallback(_mainWindow, GLFWEventHandler::onGLFWframebuffersize);
glfwSetWindowSizeCallback(_mainWindow, GLFWEventHandler::onGLFWWindowSizeFunCallback);
glfwSetWindowIconifyCallback(_mainWindow, GLFWEventHandler::onGLFWWindowIconifyCallback);
glfwSetWindowFocusCallback(_mainWindow, GLFWEventHandler::onGLFWWindowFocusCallback);
setFrameSize(rect.size.width, rect.size.height);
// check OpenGL version at first
const GLubyte* glVersion = glGetString(GL_VERSION);
if ( utils::atof((const char*)glVersion) < 1.5 )
{
char strComplain[256] = {0};
sprintf(strComplain,
"OpenGL 1.5 or higher is required (your version is %s). Please upgrade the driver of your video card.",
glVersion);
MessageBox(strComplain, "OpenGL version too old");
return false;
}
initGlew();
// Enable point size by default.
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
if(_glContextAttrs.multisamplingCount > 0)
glEnable(GL_MULTISAMPLE);
// // GLFW v3.2 no longer emits "onGLFWWindowSizeFunCallback" at creation time. Force default viewport:
// setViewPortInPoints(0, 0, neededWidth, neededHeight);
//
return true;
}
上述完成了各种事件的注册,事件回调的方法都在GLViewHandler中实现,并最终调用到GLViewlmpl中实现的处理各种事件的方法:
class CC_DLL GLFWEventHandler
{
public:
static void onGLFWError(int errorID, const char* errorDesc)
{
if (_view)
_view->onGLFWError(errorID, errorDesc);
}
static void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify)
{
if (_view)
_view->onGLFWMouseCallBack(window, button, action, modify);
}
static void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y)
{
if (_view)
_view->onGLFWMouseMoveCallBack(window, x, y);
}
static void onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y)
{
if (_view)
_view->onGLFWMouseScrollCallback(window, x, y);
}
static void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (_view)
_view->onGLFWKeyCallback(window, key, scancode, action, mods);
}
static void onGLFWCharCallback(GLFWwindow* window, unsigned int character)
{
if (_view)
_view->onGLFWCharCallback(window, character);
}
static void onGLFWWindowPosCallback(GLFWwindow* windows, int x, int y)
{
if (_view)
_view->onGLFWWindowPosCallback(windows, x, y);
}
static void onGLFWframebuffersize(GLFWwindow* window, int w, int h)
{
if (_view)
_view->onGLFWframebuffersize(window, w, h);
}
static void onGLFWWindowSizeFunCallback(GLFWwindow *window, int width, int height)
{
if (_view)
_view->onGLFWWindowSizeFunCallback(window, width, height);
}
static void setGLViewImpl(GLViewImpl* view)
{
_view = view;
}
static void onGLFWWindowIconifyCallback(GLFWwindow* window, int iconified)
{
if (_view)
{
_view->onGLFWWindowIconifyCallback(window, iconified);
}
}
static void onGLFWWindowFocusCallback(GLFWwindow* window, int focused)
{
if (_view)
{
_view->onGLFWWindowFocusCallback(window, focused);
}
}
private:
static GLViewImpl* _view;
};
// GLFW callbacks
void onGLFWError(int errorID, const char* errorDesc);
void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify);
void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y);
void onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y);
void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
void onGLFWCharCallback(GLFWwindow* window, unsigned int character);
void onGLFWWindowPosCallback(GLFWwindow* windows, int x, int y);
void onGLFWframebuffersize(GLFWwindow* window, int w, int h);
void onGLFWWindowSizeFunCallback(GLFWwindow *window, int width, int height);
void onGLFWWindowIconifyCallback(GLFWwindow* window, int iconified);
void onGLFWWindowFocusCallback(GLFWwindow* window, int focused);
上面的一系列操作完成了,窗口的绘制,事件的注册,然后通过pollEvents()方法便可以监听事件的发生:
void GLViewImpl::pollEvents()
{
glfwPollEvents();
}
接下来我们回到Application::run()方法中,在run()方法中,完成了窗口的绘制和事件注册之后,会进入一个while()循环,如果窗口没有关闭且到了当前帧的执行时间,就会调用CCDirector的mainLoop()方法:
while(!glview->windowShouldClose())
{
QueryPerformanceCounter(&nNow);
interval = nNow.QuadPart - nLast.QuadPart;
if (interval >= _animationInterval.QuadPart)
{
nLast.QuadPart = nNow.QuadPart;
director->mainLoop();
glview->pollEvents();
}
else
{
// The precision of timer on Windows is set to highest (1ms) by 'timeBeginPeriod' from above code,
// but it's still not precise enough. For example, if the precision of timer is 1ms,
// Sleep(3) may make a sleep of 2ms or 4ms. Therefore, we subtract 1ms here to make Sleep time shorter.
// If 'waitMS' is equal or less than 1ms, don't sleep and run into next loop to
// boost CPU to next frame accurately.
waitMS = (_animationInterval.QuadPart - interval) * 1000LL / freq.QuadPart - 1L;
if (waitMS > 1L)
Sleep(waitMS);
}
}
void Director::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (_restartDirectorInNextLoop)
{
_restartDirectorInNextLoop = false;
restartDirector();
}
else if (! _invalid)
{
drawScene();
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
在每一帧中都会调用drawScene(),此方法的作用主要是完成帧的绘制和事件的监听以及分发:
void Director::drawScene()
{
// calculate "global" dt
calculateDeltaTime();
if (_openGLView)
{
_openGLView->pollEvents();
}
//tick before glClear: issue #533
if (! _paused)
{
_eventDispatcher->dispatchEvent(_eventBeforeUpdate);
_scheduler->update(_deltaTime);
_eventDispatcher->dispatchEvent(_eventAfterUpdate);
}
_renderer->clear();
experimental::FrameBuffer::clearAllFBOs();
_eventDispatcher->dispatchEvent(_eventBeforeDraw);
/* to avoid flickr, nextScene MUST be here: after tick and before draw.
* FIXME: Which bug is this one. It seems that it can't be reproduced with v0.9
*/
if (_nextScene)
{
setNextScene();
}
pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
if (_runningScene)
{
#if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH)
_runningScene->stepPhysicsAndNavigation(_deltaTime);
#endif
//clear draw stats
_renderer->clearDrawStats();
//render the scene
if(_openGLView)
_openGLView->renderScene(_runningScene, _renderer);
_eventDispatcher->dispatchEvent(_eventAfterVisit);
}
// draw the notifications node
if (_notificationNode)
{
_notificationNode->visit(_renderer, Mat4::IDENTITY, 0);
}
updateFrameRate();
if (_displayStats)
{
#if !CC_STRIP_FPS
showStats();
#endif
}
_renderer->render();
_eventDispatcher->dispatchEvent(_eventAfterDraw);
popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
_totalFrames++;
// swap buffers
if (_openGLView)
{
_openGLView->swapBuffers();
}
if (_displayStats)
{
#if !CC_STRIP_FPS
calculateMPF();
#endif
}
}
可以看到,在方法开始的地方,会调用pollEvents()方法,这个方法,我们在前面已经说过,是为了监听事件的发生,如果窗口上发生了事件,就会调用注册的对应的事件,对应的事件在GLViewlmpl中已经实现:
void GLViewImpl::onGLFWError(int errorID, const char* errorDesc)
{
if (_mainWindow)
{
_glfwError = StringUtils::format("GLFWError #%d Happen, %s", errorID, errorDesc);
}
else
{
_glfwError.append(StringUtils::format("GLFWError #%d Happen, %s\n", errorID, errorDesc));
}
CCLOGERROR("%s", _glfwError.c_str());
}
void GLViewImpl::onGLFWMouseCallBack(GLFWwindow* /*window*/, int button, int action, int /*modify*/)
{
if(GLFW_MOUSE_BUTTON_LEFT == button)
{
if(GLFW_PRESS == action)
{
_captured = true;
if (this->getViewPortRect().equals(Rect::ZERO) || this->getViewPortRect().containsPoint(Vec2(_mouseX,_mouseY)))
{
intptr_t id = 0;
this->handleTouchesBegin(1, &id, &_mouseX, &_mouseY);
}
}
else if(GLFW_RELEASE == action)
{
if (_captured)
{
_captured = false;
intptr_t id = 0;
this->handleTouchesEnd(1, &id, &_mouseX, &_mouseY);
}
}
}
//Because OpenGL and cocos2d-x uses different Y axis, we need to convert the coordinate here
float cursorX = (_mouseX - _viewPortRect.origin.x) / _scaleX;
float cursorY = (_viewPortRect.origin.y + _viewPortRect.size.height - _mouseY) / _scaleY;
if(GLFW_PRESS == action)
{
EventMouse event(EventMouse::MouseEventType::MOUSE_DOWN);
event.setCursorPosition(cursorX, cursorY);
event.setMouseButton(static_cast<cocos2d::EventMouse::MouseButton>(button));
Director::getInstance()->getEventDispatcher()->dispatchEvent(&event);
}
else if(GLFW_RELEASE == action)
{
EventMouse event(EventMouse::MouseEventType::MOUSE_UP);
event.setCursorPosition(cursorX, cursorY);
event.setMouseButton(static_cast<cocos2d::EventMouse::MouseButton>(button));
Director::getInstance()->getEventDispatcher()->dispatchEvent(&event);
}
}
void GLViewImpl::onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y)
{
_mouseX = (float)x;
_mouseY = (float)y;
_mouseX /= this->getFrameZoomFactor();
_mouseY /= this->getFrameZoomFactor();
if (_isInRetinaMonitor)
{
if (_retinaFactor == 1)
{
_mouseX *= 2;
_mouseY *= 2;
}
}
if (_captured)
{
intptr_t id = 0;
this->handleTouchesMove(1, &id, &_mouseX, &_mouseY);
}
//Because OpenGL and cocos2d-x uses different Y axis, we need to convert the coordinate here
float cursorX = (_mouseX - _viewPortRect.origin.x) / _scaleX;
float cursorY = (_viewPortRect.origin.y + _viewPortRect.size.height - _mouseY) / _scaleY;
EventMouse event(EventMouse::MouseEventType::MOUSE_MOVE);
// Set current button
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS)
{
event.setMouseButton(static_cast<cocos2d::EventMouse::MouseButton>(GLFW_MOUSE_BUTTON_LEFT));
}
else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS)
{
event.setMouseButton(static_cast<cocos2d::EventMouse::MouseButton>(GLFW_MOUSE_BUTTON_RIGHT));
}
else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS)
{
event.setMouseButton(static_cast<cocos2d::EventMouse::MouseButton>(GLFW_MOUSE_BUTTON_MIDDLE));
}
event.setCursorPosition(cursorX, cursorY);
Director::getInstance()->getEventDispatcher()->dispatchEvent(&event);
}
void GLViewImpl::onGLFWMouseScrollCallback(GLFWwindow* /*window*/, double x, double y)
{
EventMouse event(EventMouse::MouseEventType::MOUSE_SCROLL);
//Because OpenGL and cocos2d-x uses different Y axis, we need to convert the coordinate here
float cursorX = (_mouseX - _viewPortRect.origin.x) / _scaleX;
float cursorY = (_viewPortRect.origin.y + _viewPortRect.size.height - _mouseY) / _scaleY;
event.setScrollData((float)x, -(float)y);
event.setCursorPosition(cursorX, cursorY);
Director::getInstance()->getEventDispatcher()->dispatchEvent(&event);
}
void GLViewImpl::onGLFWKeyCallback(GLFWwindow* /*window*/, int key, int /*scancode*/, int action, int /*mods*/)
{
if (GLFW_REPEAT != action)
{
EventKeyboard event(g_keyCodeMap[key], GLFW_PRESS == action);
auto dispatcher = Director::getInstance()->getEventDispatcher();
dispatcher->dispatchEvent(&event);
}
if (GLFW_RELEASE != action)
{
switch (g_keyCodeMap[key])
{
case EventKeyboard::KeyCode::KEY_BACKSPACE:
IMEDispatcher::sharedDispatcher()->dispatchDeleteBackward();
break;
case EventKeyboard::KeyCode::KEY_HOME:
case EventKeyboard::KeyCode::KEY_KP_HOME:
case EventKeyboard::KeyCode::KEY_DELETE:
case EventKeyboard::KeyCode::KEY_KP_DELETE:
case EventKeyboard::KeyCode::KEY_END:
case EventKeyboard::KeyCode::KEY_LEFT_ARROW:
case EventKeyboard::KeyCode::KEY_RIGHT_ARROW:
case EventKeyboard::KeyCode::KEY_ESCAPE:
IMEDispatcher::sharedDispatcher()->dispatchControlKey(g_keyCodeMap[key]);
break;
default:
break;
}
}
}
void GLViewImpl::onGLFWCharCallback(GLFWwindow* /*window*/, unsigned int character)
{
char16_t wcharString[2] = { (char16_t) character, 0 };
std::string utf8String;
StringUtils::UTF16ToUTF8( wcharString, utf8String );
static std::set<std::string> controlUnicode = {
"\xEF\x9C\x80", // up
"\xEF\x9C\x81", // down
"\xEF\x9C\x82", // left
"\xEF\x9C\x83", // right
"\xEF\x9C\xA8", // delete
"\xEF\x9C\xA9", // home
"\xEF\x9C\xAB", // end
"\xEF\x9C\xAC", // pageup
"\xEF\x9C\xAD", // pagedown
"\xEF\x9C\xB9" // clear
};
// Check for send control key
if (controlUnicode.find(utf8String) == controlUnicode.end())
{
IMEDispatcher::sharedDispatcher()->dispatchInsertText( utf8String.c_str(), utf8String.size() );
}
}
void GLViewImpl::onGLFWWindowPosCallback(GLFWwindow* /*window*/, int /*x*/, int /*y*/)
{
Director::getInstance()->setViewport();
}
void GLViewImpl::onGLFWframebuffersize(GLFWwindow* window, int w, int h)
{
float frameSizeW = _screenSize.width;
float frameSizeH = _screenSize.height;
float factorX = frameSizeW / w * _retinaFactor * _frameZoomFactor;
float factorY = frameSizeH / h * _retinaFactor * _frameZoomFactor;
if (std::abs(factorX - 0.5f) < FLT_EPSILON && std::abs(factorY - 0.5f) < FLT_EPSILON)
{
_isInRetinaMonitor = true;
if (_isRetinaEnabled)
{
_retinaFactor = 1;
}
else
{
_retinaFactor = 2;
}
glfwSetWindowSize(window, static_cast<int>(frameSizeW * 0.5f * _retinaFactor * _frameZoomFactor) , static_cast<int>(frameSizeH * 0.5f * _retinaFactor * _frameZoomFactor));
}
else if (std::abs(factorX - 2.0f) < FLT_EPSILON && std::abs(factorY - 2.0f) < FLT_EPSILON)
{
_isInRetinaMonitor = false;
_retinaFactor = 1;
glfwSetWindowSize(window, static_cast<int>(frameSizeW * _retinaFactor * _frameZoomFactor), static_cast<int>(frameSizeH * _retinaFactor * _frameZoomFactor));
}
}
void GLViewImpl::onGLFWWindowSizeFunCallback(GLFWwindow* /*window*/, int width, int height)
{
if (width && height && _resolutionPolicy != ResolutionPolicy::UNKNOWN)
{
Size baseDesignSize = _designResolutionSize;
ResolutionPolicy baseResolutionPolicy = _resolutionPolicy;
int frameWidth = width / _frameZoomFactor;
int frameHeight = height / _frameZoomFactor;
setFrameSize(frameWidth, frameHeight);
setDesignResolutionSize(baseDesignSize.width, baseDesignSize.height, baseResolutionPolicy);
Director::getInstance()->setViewport();
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GLViewImpl::EVENT_WINDOW_RESIZED, nullptr);
}
}
void GLViewImpl::onGLFWWindowIconifyCallback(GLFWwindow* /*window*/, int iconified)
{
if (iconified == GL_TRUE)
{
Application::getInstance()->applicationDidEnterBackground();
}
else
{
Application::getInstance()->applicationWillEnterForeground();
}
}
void GLViewImpl::onGLFWWindowFocusCallback(GLFWwindow* /*window*/, int focused)
{
if (focused == GL_TRUE)
{
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GLViewImpl::EVENT_WINDOW_FOCUSED, nullptr);
}
else
{
Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GLViewImpl::EVENT_WINDOW_UNFOCUSED, nullptr);
}
}
可以看到上述的处理方式大概有两种,一种是直接调用CCDirector中的eventDispatcher的dispatchEvent()方法,另一种是调用handleTouchXXX()方法,这些方法定义在GLView类中,不过最终的处理方法都是通过EventDispatcher来处理事件:
void GLView::handleTouchesBegin(int num, intptr_t ids[], float xs[], float ys[])
{
intptr_t id = 0;
float x = 0.0f;
float y = 0.0f;
int unusedIndex = 0;
EventTouch touchEvent;
for (int i = 0; i < num; ++i)
{
id = ids[i];
x = xs[i];
y = ys[i];
auto iter = g_touchIdReorderMap.find(id);
// it is a new touch
if (iter == g_touchIdReorderMap.end())
{
unusedIndex = getUnUsedIndex();
// The touches is more than MAX_TOUCHES ?
if (unusedIndex == -1) {
CCLOG("The touches is more than MAX_TOUCHES, unusedIndex = %d", unusedIndex);
continue;
}
Touch* touch = g_touches[unusedIndex] = new (std::nothrow) Touch();
touch->setTouchInfo(unusedIndex, (x - _viewPortRect.origin.x) / _scaleX,
(y - _viewPortRect.origin.y) / _scaleY);
CCLOGINFO("x = %f y = %f", touch->getLocationInView().x, touch->getLocationInView().y);
g_touchIdReorderMap.emplace(id, unusedIndex);
touchEvent._touches.push_back(touch);
}
}
if (touchEvent._touches.size() == 0)
{
CCLOG("touchesBegan: size = 0");
return;
}
touchEvent._eventCode = EventTouch::EventCode::BEGAN;
auto dispatcher = Director::getInstance()->getEventDispatcher();
dispatcher->dispatchEvent(&touchEvent);
}
void GLView::handleTouchesMove(int num, intptr_t ids[], float xs[], float ys[])
{
handleTouchesMove(num, ids, xs, ys, nullptr, nullptr);
}
void GLView::handleTouchesMove(int num, intptr_t ids[], float xs[], float ys[], float fs[], float ms[])
{
intptr_t id = 0;
float x = 0.0f;
float y = 0.0f;
float force = 0.0f;
float maxForce = 0.0f;
EventTouch touchEvent;
for (int i = 0; i < num; ++i)
{
id = ids[i];
x = xs[i];
y = ys[i];
force = fs ? fs[i] : 0.0f;
maxForce = ms ? ms[i] : 0.0f;
auto iter = g_touchIdReorderMap.find(id);
if (iter == g_touchIdReorderMap.end())
{
CCLOG("if the index doesn't exist, it is an error");
continue;
}
CCLOGINFO("Moving touches with id: %d, x=%f, y=%f, force=%f, maxFource=%f", (int)id, x, y, force, maxForce);
Touch* touch = g_touches[iter->second];
if (touch)
{
touch->setTouchInfo(iter->second, (x - _viewPortRect.origin.x) / _scaleX,
(y - _viewPortRect.origin.y) / _scaleY, force, maxForce);
touchEvent._touches.push_back(touch);
}
else
{
// It is error, should return.
CCLOG("Moving touches with id: %ld error", (long int)id);
return;
}
}
if (touchEvent._touches.size() == 0)
{
CCLOG("touchesMoved: size = 0");
return;
}
touchEvent._eventCode = EventTouch::EventCode::MOVED;
auto dispatcher = Director::getInstance()->getEventDispatcher();
dispatcher->dispatchEvent(&touchEvent);
}
void GLView::handleTouchesOfEndOrCancel(EventTouch::EventCode eventCode, int num, intptr_t ids[], float xs[], float ys[])
{
intptr_t id = 0;
float x = 0.0f;
float y = 0.0f;
EventTouch touchEvent;
for (int i = 0; i < num; ++i)
{
id = ids[i];
x = xs[i];
y = ys[i];
auto iter = g_touchIdReorderMap.find(id);
if (iter == g_touchIdReorderMap.end())
{
CCLOG("if the index doesn't exist, it is an error");
continue;
}
/* Add to the set to send to the director */
Touch* touch = g_touches[iter->second];
if (touch)
{
CCLOGINFO("Ending touches with id: %d, x=%f, y=%f", (int)id, x, y);
touch->setTouchInfo(iter->second, (x - _viewPortRect.origin.x) / _scaleX,
(y - _viewPortRect.origin.y) / _scaleY);
touchEvent._touches.push_back(touch);
g_touches[iter->second] = nullptr;
removeUsedIndexBit(iter->second);
g_touchIdReorderMap.erase(id);
}
else
{
CCLOG("Ending touches with id: %ld error", static_cast<long>(id));
return;
}
}
if (touchEvent._touches.size() == 0)
{
CCLOG("touchesEnded or touchesCancel: size = 0");
return;
}
touchEvent._eventCode = eventCode;
auto dispatcher = Director::getInstance()->getEventDispatcher();
dispatcher->dispatchEvent(&touchEvent);
for (auto& touch : touchEvent._touches)
{
// release the touch object.
touch->release();
}
}
void GLView::handleTouchesEnd(int num, intptr_t ids[], float xs[], float ys[])
{
handleTouchesOfEndOrCancel(EventTouch::EventCode::ENDED, num, ids, xs, ys);
}
void GLView::handleTouchesCancel(int num, intptr_t ids[], float xs[], float ys[])
{
handleTouchesOfEndOrCancel(EventTouch::EventCode::CANCELLED, num, ids, xs, ys);
}
void EventDispatcher::dispatchEvent(Event* event)
{
if (!_isEnabled)
return;
updateDirtyFlagForSceneGraph();
DispatchGuard guard(_inDispatch);
if (event->getType() == Event::Type::TOUCH)
{
dispatchTouchEvent(static_cast<EventTouch*>(event));
return;
}
auto listenerID = __getListenerID(event);
sortEventListeners(listenerID);
auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners;
if (event->getType() == Event::Type::MOUSE) {
pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners;
}
auto iter = _listenerMap.find(listenerID);
if (iter != _listenerMap.end())
{
auto listeners = iter->second;
auto onEvent = [&event](EventListener* listener) -> bool{
event->setCurrentTarget(listener->getAssociatedNode());
listener->_onEvent(event);
return event->isStopped();
};
(this->*pfnDispatchEventToListeners)(listeners, onEvent);
}
updateListeners(event);
}
这里以处理触摸事件为例,在dispatchEvent()方法中,如果事件类型是TOUCH,会调用dispatchTouchEvent()方法:
void EventDispatcher::dispatchTouchEvent(EventTouch* event)
{
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;
bool isNeedsMutableSet = (oneByOneListeners && allAtOnceListeners);
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();
for (auto& touches : originalTouches)
{
bool isSwallowed = false;
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);
bool isClaimed = false;
std::vector<Touch*>::iterator removedIter;
EventTouch::EventCode eventCode = event->getEventCode();
if (eventCode == EventTouch::EventCode::BEGAN)
{
if (listener->onTouchBegan)
{
isClaimed = listener->onTouchBegan(touches, event);
if (isClaimed && listener->_isRegistered)
{
listener->_claimedTouches.push_back(touches);
}
}
}
else if (listener->_claimedTouches.size() > 0
&& ((removedIter = std::find(listener->_claimedTouches.begin(), listener->_claimedTouches.end(), touches)) != listener->_claimedTouches.end()))
{
isClaimed = true;
switch (eventCode)
{
case EventTouch::EventCode::MOVED:
if (listener->onTouchMoved)
{
listener->onTouchMoved(touches, event);
}
break;
case EventTouch::EventCode::ENDED:
if (listener->onTouchEnded)
{
listener->onTouchEnded(touches, event);
}
if (listener->_isRegistered)
{
listener->_claimedTouches.erase(removedIter);
}
break;
case EventTouch::EventCode::CANCELLED:
if (listener->onTouchCancelled)
{
listener->onTouchCancelled(touches, 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(touches->getID() == (*mutableTouchesIter)->getID(),
"touches ID should be equal to mutableTouchesIter's ID.");
if (isClaimed && listener->_isRegistered && listener->_needSwallow)
{
if (isNeedsMutableSet)
{
mutableTouchesIter = mutableTouches.erase(mutableTouchesIter);
isSwallowed = true;
}
return true;
}
return false;
};
//
dispatchTouchEventToListeners(oneByOneListeners, onTouchEvent);
if (event->isStopped())
{
return;
}
if (!isSwallowed)
++mutableTouchesIter;
}
}
//
// process standard handlers 2nd
//
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;
};
dispatchTouchEventToListeners(allAtOnceListeners, onTouchesEvent);
if (event->isStopped())
{
return;
}
}
updateListeners(event);
}
touch事件要分两种进行处理:一是单点触摸,另一种是多点触摸,并且处理方式也都被封装起来,作为参数传递给dispatchTouchEventToListeners()方法,然后通过onEvent()的方式调用:
void EventDispatcher::dispatchTouchEventToListeners(EventListenerVector* listeners, const std::function<bool(EventListener*)>& onEvent)
{
bool shouldStopPropagation = false;
auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();
ssize_t i = 0;
// priority < 0
if (fixedPriorityListeners)
{
CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()), "Out of range exception!");
if (!fixedPriorityListeners->empty())
{
for (; i < listeners->getGt0Index(); ++i)
{
auto l = fixedPriorityListeners->at(i);
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
}
}
auto scene = Director::getInstance()->getRunningScene();
if (scene && sceneGraphPriorityListeners)
{
if (!shouldStopPropagation)
{
// priority == 0, scene graph priority
// first, get all enabled, unPaused and registered listeners
std::vector<EventListener*> sceneListeners;
for (auto& l : *sceneGraphPriorityListeners)
{
if (l->isEnabled() && !l->isPaused() && l->isRegistered())
{
sceneListeners.push_back(l);
}
}
// second, for all camera call all listeners
// get a copy of cameras, prevent it's been modified in listener callback
// if camera's depth is greater, process it earlier
auto cameras = scene->getCameras();
for (auto rit = cameras.rbegin(), ritRend = cameras.rend(); rit != ritRend; ++rit)
{
Camera* camera = *rit;
if (camera->isVisible() == false)
{
continue;
}
Camera::_visitingCamera = camera;
auto cameraFlag = (unsigned short)camera->getCameraFlag();
for (auto& l : sceneListeners)
{
if (nullptr == l->getAssociatedNode() || 0 == (l->getAssociatedNode()->getCameraMask() & cameraFlag))
{
continue;
}
if (onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
if (shouldStopPropagation)
{
break;
}
}
Camera::_visitingCamera = nullptr;
}
}
if (fixedPriorityListeners)
{
if (!shouldStopPropagation)
{
// priority > 0
ssize_t size = fixedPriorityListeners->size();
for (; i < size; ++i)
{
auto l = fixedPriorityListeners->at(i);
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
}
}
}
上述操作完成了窗口的绘制、窗口事件的注册和监听以及事件的分发机制。接下来就是组件如何添加监听器。
我们以UIButton为例,来看一下组件是如何添加监听器的:
首先UIButton继承自UIWidget,监听器的添加是在UIWidget中完成的:
void Widget::setTouchEnabled(bool enable)
{
if (enable == _touchEnabled)
{
return;
}
_touchEnabled = enable;
if (_touchEnabled)
{
_touchListener = EventListenerTouchOneByOne::create();
CC_SAFE_RETAIN(_touchListener);
_touchListener->setSwallowTouches(true);
_touchListener->onTouchBegan = CC_CALLBACK_2(Widget::onTouchBegan, this);
_touchListener->onTouchMoved = CC_CALLBACK_2(Widget::onTouchMoved, this);
_touchListener->onTouchEnded = CC_CALLBACK_2(Widget::onTouchEnded, this);
_touchListener->onTouchCancelled = CC_CALLBACK_2(Widget::onTouchCancelled, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(_touchListener, this);
}
else
{
_eventDispatcher->removeEventListener(_touchListener);
CC_SAFE_RELEASE_NULL(_touchListener);
}
}
UIWidget类被设置为可触摸的时候,添加监听器到自身的_eventDispatcher字段中,而_eventDispatcher在Node构造时就被初始化为CCDirector的_eventDispatcher字段:
Node::Node()
: _rotationX(0.0f)
, _rotationY(0.0f)
, _rotationZ_X(0.0f)
, _rotationZ_Y(0.0f)
, _scaleX(1.0f)
, _scaleY(1.0f)
, _scaleZ(1.0f)
, _positionZ(0.0f)
, _usingNormalizedPosition(false)
, _normalizedPositionDirty(false)
, _skewX(0.0f)
, _skewY(0.0f)
, _anchorPoint(0, 0)
, _contentSize(Size::ZERO)
, _contentSizeDirty(true)
, _transformDirty(true)
, _inverseDirty(true)
, _additionalTransform(nullptr)
, _additionalTransformDirty(false)
, _transformUpdated(true)
// children (lazy allocs)
// lazy alloc
, _localZOrder$Arrival(0LL)
, _globalZOrder(0)
, _parent(nullptr)
// "whole screen" objects. like Scenes and Layers, should set _ignoreAnchorPointForPosition to true
, _tag(Node::INVALID_TAG)
, _name("")
, _hashOfName(0)
// userData is always inited as nil
, _userData(nullptr)
, _userObject(nullptr)
, _glProgramState(nullptr)
, _running(false)
, _visible(true)
, _ignoreAnchorPointForPosition(false)
, _reorderChildDirty(false)
, _isTransitionFinished(false)
#if CC_ENABLE_SCRIPT_BINDING
, _updateScriptHandler(0)
#endif
, _componentContainer(nullptr)
, _displayedOpacity(255)
, _realOpacity(255)
, _displayedColor(Color3B::WHITE)
, _realColor(Color3B::WHITE)
, _cascadeColorEnabled(false)
, _cascadeOpacityEnabled(false)
, _cameraMask(1)
, _onEnterCallback(nullptr)
, _onExitCallback(nullptr)
, _onEnterTransitionDidFinishCallback(nullptr)
, _onExitTransitionDidStartCallback(nullptr)
#if CC_USE_PHYSICS
, _physicsBody(nullptr)
#endif
{
// set default scheduler and actionManager
_director = Director::getInstance();
_actionManager = _director->getActionManager();
_actionManager->retain();
_scheduler = _director->getScheduler();
_scheduler->retain();
_eventDispatcher = _director->getEventDispatcher();
_eventDispatcher->retain();
#if CC_ENABLE_SCRIPT_BINDING
ScriptEngineProtocol* engine = ScriptEngineManager::getInstance()->getScriptEngine();
_scriptType = engine != nullptr ? engine->getScriptType() : kScriptTypeNone;
#endif
_transform = _inverse = Mat4::IDENTITY;
}
在我们构造一个UIButton组件的时候,首先调用在父类UIWidget中实现的setTouchEnabled()方法:
Button::Button():
_buttonNormalRenderer(nullptr),
_buttonClickedRenderer(nullptr),
_buttonDisabledRenderer(nullptr),
_titleRenderer(nullptr),
_zoomScale(0.1f),
_prevIgnoreSize(true),
_scale9Enabled(false),
_pressedActionEnabled(false),
_capInsetsNormal(Rect::ZERO),
_capInsetsPressed(Rect::ZERO),
_capInsetsDisabled(Rect::ZERO),
_normalTextureSize(_contentSize),
_pressedTextureSize(_contentSize),
_disabledTextureSize(_contentSize),
_normalTextureLoaded(false),
_pressedTextureLoaded(false),
_disabledTextureLoaded(false),
_normalTextureAdaptDirty(true),
_pressedTextureAdaptDirty(true),
_disabledTextureAdaptDirty(true),
_normalFileName(""),
_clickedFileName(""),
_disabledFileName(""),
_normalTexType(TextureResType::LOCAL),
_pressedTexType(TextureResType::LOCAL),
_disabledTexType(TextureResType::LOCAL),
_fontName("")
{
setTouchEnabled(true);
}
上述步骤便完成了,UIButton的事件监听器的添加,至于具体事件对应的回调方法也在UIWidget中实现:
bool Widget::onTouchBegan(Touch *touch, Event* /*unusedEvent*/)
{
_hitted = false;
if (isVisible() && isEnabled() && isAncestorsEnabled() && isAncestorsVisible(this) )
{
_touchBeganPosition = touch->getLocation();
auto camera = Camera::getVisitingCamera();
if(hitTest(_touchBeganPosition, camera, nullptr))
{
if (isClippingParentContainsPoint(_touchBeganPosition)) {
_hittedByCamera = camera;
_hitted = true;
}
}
}
if (!_hitted)
{
return false;
}
setHighlighted(true);
/*
* Propagate touch events to its parents
*/
if (_propagateTouchEvents)
{
this->propagateTouchEvent(TouchEventType::BEGAN, this, touch);
}
pushDownEvent();
return true;
}
void Widget::onTouchMoved(Touch *touch, Event* /*unusedEvent*/)
{
_touchMovePosition = touch->getLocation();
setHighlighted(hitTest(_touchMovePosition, _hittedByCamera, nullptr));
/*
* Propagate touch events to its parents
*/
if (_propagateTouchEvents)
{
this->propagateTouchEvent(TouchEventType::MOVED, this, touch);
}
moveEvent();
}
void Widget::onTouchEnded(Touch *touch, Event* /*unusedEvent*/)
{
_touchEndPosition = touch->getLocation();
/*
* Propagate touch events to its parents
*/
if (_propagateTouchEvents)
{
this->propagateTouchEvent(TouchEventType::ENDED, this, touch);
}
bool highlight = _highlight;
setHighlighted(false);
if (highlight)
{
releaseUpEvent();
}
else
{
cancelUpEvent();
}
}
void Widget::onTouchCancelled(Touch* touch, Event* /*unusedEvent*/)
{
/*
* Propagate touch events to its parents
*/
if (_propagateTouchEvents)
{
this->propagateTouchEvent(TouchEventType::CANCELED, this, touch);
}
setHighlighted(false);
cancelUpEvent();
}
如果细心观察,可以注意到是没有给点击时间注册回调方法,点击事件的调用触摸事件结束的时候被调用,调用在releaseUpEvent()方法中实现。hightlight字段的意义是判断触摸结束时,触摸点是否位于组建触摸区域内,如果是则调用releaseUpEvent()方法:
void Widget::releaseUpEvent()
{
this->retain();
if (isFocusEnabled())
{
requestFocus();
}
if (_touchEventCallback)
{
_touchEventCallback(this, TouchEventType::ENDED);
}
if (_clickEventListener) {
_clickEventListener(this);
}
this->release();
}
当调用releaseUpEvent()方法时,判断一下_clickEventListener监听器有没有被添加,如果被添加,则调用对应的事件。点击事件的监听器添加方法实现在UIWidget类中:
void Widget::addClickEventListener(const ccWidgetClickCallback &callback)
{
this->_clickEventListener = callback;
}
总结:
cocos2d-x的事件机制总的来说就是:
- 进入游戏,创建窗口。
- 通过GLViewlmpl类注册窗口事件。
- 进入游戏主循环,每帧调用CCDirector的mainLoop()方法。
- 给组件添加对应事件监听器,并实现事件回调方法。
- 当窗口接受到事件,调用CCDirector的EventDispatcher变量来相应对应事件注册的回调函数,进而找到相关组件注册的事件监听器,并调用对应的回调函数。