這篇,繼續從源碼的角度賞析下Cocos2dx引擎的另外一模塊事件分發處理機制。引擎的版本是3.14。同時,也是學習總結的過程,但願經過這種方式來加深對Cocos2dx引擎的理解。若有理解錯誤的地方,還望不吝賜教。html
做者:AlphaGL。版權全部,歡迎保留原文連接進行轉載 :)java
傳送門:
Cocos2dx源碼賞析(1)之啓動流程與主循環
Cocos2dx源碼賞析(2)之渲染android
在Cocos2dx中,Event類是全部事件的基類,也是對各類事件的統一的抽象。事件的類型有以下:ios
有上面各類不一樣類型的事件,就有處理對應事件的監聽器。EventListener是全部事件監聽的基類。
(圖片出自Cocos2d-x官方文檔)app
以上是事件監聽器的繼承關係。對應的事件監聽器以下:框架
在Cocos2dx中由事件調度器EventDispatcher來統一分發事件類型,並觸發相應的事件監聽器。經過,上一篇對Cocos2dx渲染過程的分析中,可知在導演類Director方法中會初始化事件監聽器EventDispatcher,繪製場景過程當中,會響應相應的自定義事件。less
bool Director::init(void) { _eventDispatcher = new (std::nothrow) EventDispatcher(); _eventAfterDraw = new (std::nothrow) EventCustom(EVENT_AFTER_DRAW); _eventAfterDraw->setUserData(this); _eventAfterVisit = new (std::nothrow) EventCustom(EVENT_AFTER_VISIT); _eventAfterVisit->setUserData(this); _eventBeforeUpdate = new (std::nothrow) EventCustom(EVENT_BEFORE_UPDATE); _eventBeforeUpdate->setUserData(this); _eventAfterUpdate = new (std::nothrow) EventCustom(EVENT_AFTER_UPDATE); _eventAfterUpdate->setUserData(this); _eventProjectionChanged = new (std::nothrow) EventCustom(EVENT_PROJECTION_CHANGED); _eventProjectionChanged->setUserData(this); _eventResetDirector = new (std::nothrow) EventCustom(EVENT_RESET); }
在Director導演類的init方法中,初始化了事件調度器_eventDispatcher,而Director是單例的,因此,咱們老是能得到全局的事件調度器,來註冊相應的事件監聽。ide
那麼,按照調用的順序依次介紹下,以上自定義事件:
_eventBeforeUpdate:在定時器Scheduler執行update以前,事件調度器EventDispatcher會調度執行該類型的事件,即EventCustom自定義類型事件。
_eventAfterUpdate:在定時器Scheduler執行update以後,事件調度器EventDispatcher會調度執行該類型的事件,即EventCustom自定義類型事件。
_eventAfterVisit:在遍歷場景以後會執行該事件。
_eventAfterDraw:在渲染以後會執行該事件。學習
而最終都會調到EventDispatcher的dispatchEvent方法中去:ui
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中,會從從保存了以類型id爲key的Map中來遍歷註冊的監聽,並執行該監聽的callback方法。
這裏,先來看看Cocos2dx中如何對加速度事件以及相應的監聽作封裝的。以Android和iOS平臺爲例,來結合源碼來講明下,整個事件的相應過程。
在手機中,會內置一些傳感器來探測外界的信號,並將探測到的數據轉換成相應的數值信息來經過手機內的應用,來作出相應的適應變化。例若有:加速度傳感器、壓力傳感器、溫度傳感器等。
在Android中提供了SensorManager的管理類,來爲開發者提供這方面的調用。通常使用步驟爲:
(1)獲取SensorManager的實例。
SensorManager sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
(2)獲取具體類型的傳感器Sensor。
Sensor mAccelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
(3)監聽傳感器信息SensorEventListener
SensorEventListener listener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } };
onSensorChanged:
當傳感器監測到的數值發生變化時就會調用該方法。
onAccuracyChanged:
當傳感器的精度發生變化時就會調用該方法。
在Cocos2dx引擎中,爲咱們封裝了類Cocos2dxAccelerometer中會具體的實現:
@Override public void onSensorChanged(final SensorEvent sensorEvent) { if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { float x = sensorEvent.values[0]; float y = sensorEvent.values[1]; final float z = sensorEvent.values[2]; // needed by VR code this.accelerometerValues[0] = x; this.accelerometerValues[1] = y; this.accelerometerValues[2] = z; final int orientation = this.mContext.getResources().getConfiguration().orientation; if ((orientation == Configuration.ORIENTATION_LANDSCAPE) && (this.mNaturalOrientation != Surface.ROTATION_0)) { final float tmp = x; x = -y; y = tmp; } else if ((orientation == Configuration.ORIENTATION_PORTRAIT) && (this.mNaturalOrientation != Surface.ROTATION_0)) { final float tmp = x; x = y; y = -tmp; } Cocos2dxGLSurfaceView.queueAccelerometer(x,y,z,sensorEvent.timestamp); } else if (sensorEvent.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { // needed by VR code this.compassFieldValues[0] = sensorEvent.values[0]; this.compassFieldValues[1] = sensorEvent.values[1]; this.compassFieldValues[2] = sensorEvent.values[2]; } } @Override public void onAccuracyChanged(final Sensor sensor, final int accuracy) { }
在檢測傳感器監測到的數值發生變化時,會回調onSensorChanged方法,而最終會調到Cocos2dxGLSurfaceView.queueAccelerometer方法:
public static void queueAccelerometer(final float x, final float y, final float z, final long timestamp) { mCocos2dxGLSurfaceView.queueEvent(new Runnable() { @Override public void run() { Cocos2dxAccelerometer.onSensorChanged(x, y, z, timestamp); } });
而在Cocos2dxGLSurfaceView.queueAccelerometer方法中,最終會在OpenGL事件隊列queueEvent的包裝下調用Cocos2dxAccelerometer.onSensorChanged,該方法在Java_org_cocos2dx_lib_Cocos2dxAccelerometer.cpp中:
extern "C" { JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxAccelerometer_onSensorChanged(JNIEnv* env, jobject thiz, jfloat x, jfloat y, jfloat z, jlong timeStamp) { Acceleration a; a.x = -((double)x / TG3_GRAVITY_EARTH); a.y = -((double)y / TG3_GRAVITY_EARTH); a.z = -((double)z / TG3_GRAVITY_EARTH); a.timestamp = (double)timeStamp; EventAcceleration event(a); Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); } }
能夠看到給事件調度器EventDispatcher發送了一個EventAcceleration類型的事件,而且把java層傳感器獲取到的數據傳給了引擎。這便是加速度事件在Android整個的響應過程。
在iOS提供了CoreMotion框架來獲取加速度傳感器的採集的數據。通常步驟爲:
(1)初始化CoreMotion對象
_motionManager = [[CMMotionManager alloc] init];
(2)獲取更新數據
[_motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) { }];
而在Cocos2dx中,這些操做封裝在CCDevice-ios.mm文件中,並實現了CCAccelerometerDispatcher類
- (void)accelerometer:(CMAccelerometerData *)accelerometerData { _acceleration->x = accelerometerData.acceleration.x; _acceleration->y = accelerometerData.acceleration.y; _acceleration->z = accelerometerData.acceleration.z; _acceleration->timestamp = accelerometerData.timestamp; double tmp = _acceleration->x; switch ([[UIApplication sharedApplication] statusBarOrientation]) { case UIInterfaceOrientationLandscapeRight: _acceleration->x = -_acceleration->y; _acceleration->y = tmp; break; case UIInterfaceOrientationLandscapeLeft: _acceleration->x = _acceleration->y; _acceleration->y = -tmp; break; case UIInterfaceOrientationPortraitUpsideDown: _acceleration->x = -_acceleration->y; _acceleration->y = -tmp; break; case UIInterfaceOrientationPortrait: break; default: NSAssert(false, @"unknown orientation"); } cocos2d::EventAcceleration event(*_acceleration); auto dispatcher = cocos2d::Director::getInstance()->getEventDispatcher(); dispatcher->dispatchEvent(&event); }
同Android相似,最終給事件調度器EventDispatcher發送了一個EventAcceleration類型的事件,而且加速度傳感器獲取到的數據傳給了引擎。
Device::setAccelerometerEnabled(enabled); _accelerationListener = EventListenerAcceleration::create(CC_CALLBACK_2(Test::onAcceleration, this)); _eventDispatcher->addEventListenerWithSceneGraphPriority(_accelerationListener, this); void Test::onAcceleration(Acceleration* acc, Event* /*unused_event*/) { }
Cocos2dx也提供了對遊戲手柄的支持。一樣,分別以Android和iOS爲例,來結合源碼來講明下,整個事件的相應過程。
在Android中提供了InputDeviceListener類,,來實現對多輸入設備監聽:
InputDeviceListener mInputDeviceListener = new InputDeviceListener() { public void onInputDeviceRemoved(int deviceId) { } public void onInputDeviceChanged(int deviceId) { } public void onInputDeviceAdded(int deviceId) { } };
onInputDeviceRemoved:設備移除時調用。
onInputDeviceChanged:設備狀態發生變化時調用
onInputDeviceAdded:設備鏈接上時調用。
而在Cocos2dx中,封裝在了GameControllerActivity類中:
@Override public void onInputDeviceAdded(int deviceId) { Log.d(TAG,"onInputDeviceAdded:" + deviceId); mControllerHelper.onInputDeviceAdded(deviceId); } @Override public void onInputDeviceChanged(int deviceId) { Log.w(TAG,"onInputDeviceChanged:" + deviceId); } @Override public void onInputDeviceRemoved(int deviceId) { Log.d(TAG,"onInputDeviceRemoved:" + deviceId); mControllerHelper.onInputDeviceRemoved(deviceId); }
在onInputDeviceAdded和onInputDeviceRemoved方法中,都會調到GameControllerHelper類中對應的方法:
void onInputDeviceAdded(int deviceId){ try { InputDevice device = InputDevice.getDevice(deviceId); int deviceSource = device.getSources(); if ( ((deviceSource & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || ((deviceSource & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK) ) { String deviceName = device.getName(); mGameController.append(deviceId, deviceName); GameControllerAdapter.onConnected(deviceName, deviceId); } } catch (Exception e) { e.printStackTrace(); } } void onInputDeviceChanged(int deviceId){ gatherControllers(mGameController); } void onInputDeviceRemoved(int deviceId) { if (mGameController.get(deviceId) != null) { GameControllerAdapter.onDisconnected(mGameController.get(deviceId), deviceId); mGameController.delete(deviceId); } } static void gatherControllers(SparseArray<String> controllers){ int controllerCount = controllers.size(); for (int i = 0; i < controllerCount; i++) { try { int controllerDeveceId = controllers.keyAt(i); InputDevice device = InputDevice.getDevice(controllerDeveceId); if (device == null) { GameControllerAdapter.onDisconnected(controllers.get(controllerDeveceId), controllerDeveceId); controllers.delete(controllerDeveceId); } } catch (Exception e) { int controllerDeveceId = controllers.keyAt(i); GameControllerAdapter.onDisconnected(controllers.get(controllerDeveceId), controllerDeveceId); controllers.delete(controllerDeveceId); e.printStackTrace(); } } }
這裏,又會調用到GameControllerAdapter中封裝的對應的方法:
public static void onConnected(final String vendorName, final int controller) { Cocos2dxHelper.runOnGLThread(new Runnable() { @Override public void run() { nativeControllerConnected(vendorName, controller); } }); } public static void onDisconnected(final String vendorName, final int controller) { Cocos2dxHelper.runOnGLThread(new Runnable() { @Override public void run() { nativeControllerDisconnected(vendorName, controller); } }); } public static void onButtonEvent(final String vendorName, final int controller, final int button, final boolean isPressed, final float value, final boolean isAnalog) { Cocos2dxHelper.runOnGLThread(new Runnable() { @Override public void run() { nativeControllerButtonEvent(vendorName, controller, button, isPressed, value, isAnalog); } }); } public static void onAxisEvent(final String vendorName, final int controller, final int axisID, final float value, final boolean isAnalog) { Cocos2dxHelper.runOnGLThread(new Runnable() { @Override public void run() { nativeControllerAxisEvent(vendorName, controller, axisID, value, isAnalog); } }); } private static native void nativeControllerConnected(final String vendorName, final int controller); private static native void nativeControllerDisconnected(final String vendorName, final int controller); private static native void nativeControllerButtonEvent(final String vendorName, final int controller, final int button, final boolean isPressed, final float value, final boolean isAnalog); private static native void nativeControllerAxisEvent(final String vendorName, final int controller, final int axisID, final float value, final boolean isAnalog);
能夠看到最終會調用這些native方法,把相應的數據和狀態傳到遊戲引擎,在CCController-android.cpp中:
extern "C" { void Java_org_cocos2dx_lib_GameControllerAdapter_nativeControllerConnected(JNIEnv* env, jobject thiz, jstring deviceName, jint controllerID) { CCLOG("controller id: %d connected!", controllerID); cocos2d::ControllerImpl::onConnected(cocos2d::JniHelper::jstring2string(deviceName), controllerID); } void Java_org_cocos2dx_lib_GameControllerAdapter_nativeControllerDisconnected(JNIEnv* env, jobject thiz, jstring deviceName, jint controllerID) { CCLOG("controller id: %d disconnected!", controllerID); cocos2d::ControllerImpl::onDisconnected(cocos2d::JniHelper::jstring2string(deviceName), controllerID); } void Java_org_cocos2dx_lib_GameControllerAdapter_nativeControllerButtonEvent(JNIEnv* env, jobject thiz, jstring deviceName, jint controllerID, jint button, jboolean isPressed, jfloat value, jboolean isAnalog) { cocos2d::ControllerImpl::onButtonEvent(cocos2d::JniHelper::jstring2string(deviceName), controllerID, button, isPressed, value, isAnalog); } void Java_org_cocos2dx_lib_GameControllerAdapter_nativeControllerAxisEvent(JNIEnv* env, jobject thiz, jstring deviceName, jint controllerID, jint axis, jfloat value, jboolean isAnalog) { cocos2d::ControllerImpl::onAxisEvent(cocos2d::JniHelper::jstring2string(deviceName), controllerID, axis, value, isAnalog); } }
而ControllerImpl類的實現爲:
static void onConnected(const std::string& deviceName, int deviceId) { // Check whether the controller is already connected. CCLOG("onConnected %s,%d", deviceName.c_str(),deviceId); auto iter = findController(deviceName, deviceId); if (iter != Controller::s_allController.end()) return; // It's a new controller being connected. auto controller = new cocos2d::Controller(); controller->_deviceId = deviceId; controller->_deviceName = deviceName; Controller::s_allController.push_back(controller); controller->onConnected(); } static void onDisconnected(const std::string& deviceName, int deviceId) { CCLOG("onDisconnected %s,%d", deviceName.c_str(),deviceId); auto iter = findController(deviceName, deviceId); if (iter == Controller::s_allController.end()) { CCLOGERROR("Could not find the controller!"); return; } (*iter)->onDisconnected(); Controller::s_allController.erase(iter); } static void onButtonEvent(const std::string& deviceName, int deviceId, int keyCode, bool isPressed, float value, bool isAnalog) { auto iter = findController(deviceName, deviceId); if (iter == Controller::s_allController.end()) { CCLOG("onButtonEvent:connect new controller."); onConnected(deviceName, deviceId); iter = findController(deviceName, deviceId); } (*iter)->onButtonEvent(keyCode, isPressed, value, isAnalog); } static void onAxisEvent(const std::string& deviceName, int deviceId, int axisCode, float value, bool isAnalog) { auto iter = findController(deviceName, deviceId); if (iter == Controller::s_allController.end()) { CCLOG("onAxisEvent:connect new controller."); onConnected(deviceName, deviceId); iter = findController(deviceName, deviceId); } (*iter)->onAxisEvent(axisCode, value, isAnalog); }
最後都會調用:
void Controller::onConnected() { _connectEvent->setConnectStatus(true); _eventDispatcher->dispatchEvent(_connectEvent); } void Controller::onDisconnected() { _connectEvent->setConnectStatus(false); _eventDispatcher->dispatchEvent(_connectEvent); delete this; } void Controller::onButtonEvent(int keyCode, bool isPressed, float value, bool isAnalog) { _allKeyPrevStatus[keyCode] = _allKeyStatus[keyCode]; _allKeyStatus[keyCode].isPressed = isPressed; _allKeyStatus[keyCode].value = value; _allKeyStatus[keyCode].isAnalog = isAnalog; _keyEvent->setKeyCode(keyCode); _eventDispatcher->dispatchEvent(_keyEvent); } void Controller::onAxisEvent(int axisCode, float value, bool isAnalog) { _allKeyPrevStatus[axisCode] = _allKeyStatus[axisCode]; _allKeyStatus[axisCode].value = value; _allKeyStatus[axisCode].isAnalog = isAnalog; _axisEvent->setKeyCode(axisCode); _eventDispatcher->dispatchEvent(_axisEvent); }
一樣,也是給事件調度器eventDispatcher發送對應的事件。便完成了從事件從觸發到執行的過程。
在iOS提供了GCController類處理遊戲手柄相關的操做。通常步驟爲:
(1)引入GCController頭文件
(2)註冊相應的消息通知:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onControllerConnected:) name:GCControllerDidConnectNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onControllerDisconnected:) name:GCControllerDidDisconnectNotification object:nil];
在Cocos2dx中的代碼在CCController-apple.mm中:
void Controller::startDiscoveryController() { if (NSClassFromString(@"GCController") == nil) { return; } [GCController startWirelessControllerDiscoveryWithCompletionHandler: nil]; [[GCControllerConnectionEventHandler getInstance] observerConnection: ^(GCController* gcController) { auto controller = new (std::nothrow) Controller(); controller->_impl->_gcController = gcController; controller->_deviceName = [gcController.vendorName UTF8String]; s_allController.push_back(controller); controller->registerListeners(); controller->getDeviceName(); controller->onConnected(); } disconnection: ^(GCController* gcController) { auto iter = std::find_if(s_allController.begin(), s_allController.end(), [gcController](Controller* c){ return c->_impl->_gcController == gcController; }); if(iter == s_allController.end()) { log("disconnect:Could not find the controller"); return; } (*iter)->onDisconnected(); s_allController.erase(iter); }]; }
最終,依然是調到CCController中對應的方法,這裏就再也不贅述了。
_listener = EventListenerController::create(); _listener->onConnected = CC_CALLBACK_2(Test::onConnectController,this); _listener->onDisconnected = CC_CALLBACK_2(Test::onDisconnectedController,this); _listener->onKeyDown = CC_CALLBACK_3(Test::onKeyDown, this); _listener->onKeyUp = CC_CALLBACK_3(Test::onKeyUp, this); _listener->onAxisEvent = CC_CALLBACK_3(Test::onAxisEvent, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(_listener, this); Controller::startDiscoveryController();
自定義事件是Coco2dx擴展的,方便咱們在特定的時候,執行相應的操做的事件。這裏就直接應用爲:
Director::getInstance()->getEventDispatcher()->addCustomEventListener(Director::EVENT_AFTER_DRAW, [](EventCustom* event) { auto director = Director::getInstance(); director->getEventDispatcher()->removeEventListener((EventListener*)(s_captureScreenListener)); s_captureScreenListener = nullptr; director->getRenderer()->addCommand(&s_captureScreenCommand); director->getRenderer()->render(); });
由於,焦點事件是在接受到按鍵事件時,來處理得到焦點仍是失去焦點的。因此,這兩個事件放在一塊兒介紹。
Android平臺的按鍵事件的處理,在Cocos2dxGLSurfaceView中:
@Override public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent) { switch (pKeyCode) { case KeyEvent.KEYCODE_BACK: Cocos2dxVideoHelper.mVideoHandler.sendEmptyMessage(Cocos2dxVideoHelper.KeyEventBack); case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_RIGHT: case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_DPAD_CENTER: this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyDown(pKeyCode); } }); return true; default: return super.onKeyDown(pKeyCode, pKeyEvent); } } @Override public boolean onKeyUp(final int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_RIGHT: case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_DPAD_CENTER: this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyUp(keyCode); } }); return true; default: return super.onKeyUp(keyCode, event); } }
經過jni的方式,最後會調到TouchJni中的方法(注意:這裏native方法的實現並不在對應的Cocos2dxRenderer.cpp中):
JNIEXPORT jboolean JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeKeyEvent(JNIEnv * env, jobject thiz, jint keyCode, jboolean isPressed) { Director* pDirector = Director::getInstance(); auto iterKeyCode = g_keyCodeMap.find(keyCode); if (iterKeyCode == g_keyCodeMap.end()) { return JNI_FALSE; } cocos2d::EventKeyboard::KeyCode cocos2dKey = g_keyCodeMap.at(keyCode); cocos2d::EventKeyboard event(cocos2dKey, isPressed); cocos2d::Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); return JNI_TRUE; }}
因爲iOS並不支持EventKeyboard監聽。因此,在iOS平臺下,註冊了EventListenerKeyboard監聽,但並無發送EventKeyboard事件,所以,主循環並不會執行相應的回調(onKeyPressed、onKeyReleased等)事件。
auto listener = EventListenerKeyboard::create(); listener->onKeyPressed = [](EventKeyboard::KeyCode keyCode, Event* event){ }; listener->onKeyReleased = [](EventKeyboard::KeyCode keyCode, Event* event){ };
焦點事件主要是在Widget中處理的,來設置當前控件的焦點和下一個焦點:
void Widget::dispatchFocusEvent(cocos2d::ui::Widget *widgetLoseFocus, cocos2d::ui::Widget *widgetGetFocus) { //if the widgetLoseFocus doesn't get focus, it will use the previous focused widget instead if (widgetLoseFocus && !widgetLoseFocus->isFocused()) { widgetLoseFocus = _focusedWidget; } if (widgetGetFocus != widgetLoseFocus) { if (widgetGetFocus) { widgetGetFocus->onFocusChanged(widgetLoseFocus, widgetGetFocus); } if (widgetLoseFocus) { widgetLoseFocus->onFocusChanged(widgetLoseFocus, widgetGetFocus); } EventFocus event(widgetLoseFocus, widgetGetFocus); auto dispatcher = cocos2d::Director::getInstance()->getEventDispatcher(); dispatcher->dispatchEvent(&event); } }
觸摸事件也得分Android和iOS來談,這些也一樣是經過物理接口在相應的平臺上進行捕獲來傳遞到Cocos2dx引擎中。
在Android中能夠重寫View視圖的onTouchEvent。那麼在Cocos2dx引擎中,封裝在Cocos2dxGLSurfaceView中:
@Override public boolean onTouchEvent(final MotionEvent pMotionEvent) { // these data are used in ACTION_MOVE and ACTION_CANCEL final int pointerNumber = pMotionEvent.getPointerCount(); final int[] ids = new int[pointerNumber]; final float[] xs = new float[pointerNumber]; final float[] ys = new float[pointerNumber]; if (mSoftKeyboardShown){ InputMethodManager imm = (InputMethodManager)this.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); View view = ((Activity)this.getContext()).getCurrentFocus(); imm.hideSoftInputFromWindow(view.getWindowToken(),0); this.requestFocus(); mSoftKeyboardShown = false; } for (int i = 0; i < pointerNumber; i++) { ids[i] = pMotionEvent.getPointerId(i); xs[i] = pMotionEvent.getX(i); ys[i] = pMotionEvent.getY(i); } switch (pMotionEvent.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_POINTER_DOWN: final int indexPointerDown = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; if (!mMultipleTouchEnabled && indexPointerDown != 0) { break; } final int idPointerDown = pMotionEvent.getPointerId(indexPointerDown); final float xPointerDown = pMotionEvent.getX(indexPointerDown); final float yPointerDown = pMotionEvent.getY(indexPointerDown); this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionDown(idPointerDown, xPointerDown, yPointerDown); } }); break; case MotionEvent.ACTION_DOWN: // there are only one finger on the screen final int idDown = pMotionEvent.getPointerId(0); final float xDown = xs[0]; final float yDown = ys[0]; this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionDown(idDown, xDown, yDown); } }); break; case MotionEvent.ACTION_MOVE: if (!mMultipleTouchEnabled) { // handle only touch with id == 0 for (int i = 0; i < pointerNumber; i++) { if (ids[i] == 0) { final int[] idsMove = new int[]{0}; final float[] xsMove = new float[]{xs[i]}; final float[] ysMove = new float[]{ys[i]}; this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionMove(idsMove, xsMove, ysMove); } }); break; } } } else { this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionMove(ids, xs, ys); } }); } break; case MotionEvent.ACTION_POINTER_UP: final int indexPointUp = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; if (!mMultipleTouchEnabled && indexPointUp != 0) { break; } final int idPointerUp = pMotionEvent.getPointerId(indexPointUp); final float xPointerUp = pMotionEvent.getX(indexPointUp); final float yPointerUp = pMotionEvent.getY(indexPointUp); this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionUp(idPointerUp, xPointerUp, yPointerUp); } }); break; case MotionEvent.ACTION_UP: // there are only one finger on the screen final int idUp = pMotionEvent.getPointerId(0); final float xUp = xs[0]; final float yUp = ys[0]; this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionUp(idUp, xUp, yUp); } }); break; case MotionEvent.ACTION_CANCEL: if (!mMultipleTouchEnabled) { // handle only touch with id == 0 for (int i = 0; i < pointerNumber; i++) { if (ids[i] == 0) { final int[] idsCancel = new int[]{0}; final float[] xsCancel = new float[]{xs[i]}; final float[] ysCancel = new float[]{ys[i]}; this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionCancel(idsCancel, xsCancel, ysCancel); } }); break; } } } else { this.queueEvent(new Runnable() { @Override public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionCancel(ids, xs, ys); } }); } break; } /* if (BuildConfig.DEBUG) { Cocos2dxGLSurfaceView.dumpMotionEvent(pMotionEvent); } */ return true; }
觸摸事件通常包括,按下,移動,擡起,取消等。固然,仍是多點觸摸相關的。能夠發現最終,都調到了Cocos2dxRenderer中:
public void handleActionDown(final int id, final float x, final float y) { Cocos2dxRenderer.nativeTouchesBegin(id, x, y); } public void handleActionUp(final int id, final float x, final float y) { Cocos2dxRenderer.nativeTouchesEnd(id, x, y); } public void handleActionCancel(final int[] ids, final float[] xs, final float[] ys) { Cocos2dxRenderer.nativeTouchesCancel(ids, xs, ys); } public void handleActionMove(final int[] ids, final float[] xs, final float[] ys) { Cocos2dxRenderer.nativeTouchesMove(ids, xs, ys); }
跟按鍵事件同樣,這裏也會最終調到TouchesJni中:
extern "C" { JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesBegin(JNIEnv * env, jobject thiz, jint id, jfloat x, jfloat y) { intptr_t idlong = id; cocos2d::Director::getInstance()->getOpenGLView()->handleTouchesBegin(1, &idlong, &x, &y); } JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesEnd(JNIEnv * env, jobject thiz, jint id, jfloat x, jfloat y) { intptr_t idlong = id; cocos2d::Director::getInstance()->getOpenGLView()->handleTouchesEnd(1, &idlong, &x, &y); } JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesMove(JNIEnv * env, jobject thiz, jintArray ids, jfloatArray xs, jfloatArray ys) { int size = env->GetArrayLength(ids); jint id[size]; jfloat x[size]; jfloat y[size]; env->GetIntArrayRegion(ids, 0, size, id); env->GetFloatArrayRegion(xs, 0, size, x); env->GetFloatArrayRegion(ys, 0, size, y); intptr_t idlong[size]; for(int i = 0; i < size; i++) idlong[i] = id[i]; cocos2d::Director::getInstance()->getOpenGLView()->handleTouchesMove(size, idlong, x, y); } JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesCancel(JNIEnv * env, jobject thiz, jintArray ids, jfloatArray xs, jfloatArray ys) { int size = env->GetArrayLength(ids); jint id[size]; jfloat x[size]; jfloat y[size]; env->GetIntArrayRegion(ids, 0, size, id); env->GetFloatArrayRegion(xs, 0, size, x); env->GetFloatArrayRegion(ys, 0, size, y); intptr_t idlong[size]; for(int i = 0; i < size; i++) idlong[i] = id[i]; cocos2d::Director::getInstance()->getOpenGLView()->handleTouchesCancel(size, idlong, x, y); }
一樣的邏輯,也是給事件調度器eventDispatcher發送對應的事件。便完成了從事件從觸發到執行的過程。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (isKeyboardShown_) { [self handleTouchesAfterKeyboardShow]; } UITouch* ids[IOS_MAX_TOUCHES_COUNT] = {0}; float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f}; int i = 0; for (UITouch *touch in touches) { ids[i] = touch; xs[i] = [touch locationInView: [touch view]].x * self.contentScaleFactor; ys[i] = [touch locationInView: [touch view]].y * self.contentScaleFactor; ++i; } auto glview = cocos2d::Director::getInstance()->getOpenGLView(); glview->handleTouchesBegin(i, (intptr_t*)ids, xs, ys); } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch* ids[IOS_MAX_TOUCHES_COUNT] = {0}; float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f}; float fs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; float ms[IOS_MAX_TOUCHES_COUNT] = {0.0f}; int i = 0; for (UITouch *touch in touches) { ids[i] = touch; xs[i] = [touch locationInView: [touch view]].x * self.contentScaleFactor; ys[i] = [touch locationInView: [touch view]].y * self.contentScaleFactor; #if defined(__IPHONE_9_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0) // running on iOS 9.0 or higher version if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0f) { fs[i] = touch.force; ms[i] = touch.maximumPossibleForce; } #endif ++i; } auto glview = cocos2d::Director::getInstance()->getOpenGLView(); glview->handleTouchesMove(i, (intptr_t*)ids, xs, ys, fs, ms); } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch* ids[IOS_MAX_TOUCHES_COUNT] = {0}; float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f}; int i = 0; for (UITouch *touch in touches) { ids[i] = touch; xs[i] = [touch locationInView: [touch view]].x * self.contentScaleFactor; ys[i] = [touch locationInView: [touch view]].y * self.contentScaleFactor; ++i; } auto glview = cocos2d::Director::getInstance()->getOpenGLView(); glview->handleTouchesEnd(i, (intptr_t*)ids, xs, ys); } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { UITouch* ids[IOS_MAX_TOUCHES_COUNT] = {0}; float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f}; float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f}; int i = 0; for (UITouch *touch in touches) { ids[i] = touch; xs[i] = [touch locationInView: [touch view]].x * self.contentScaleFactor; ys[i] = [touch locationInView: [touch view]].y * self.contentScaleFactor; ++i; } auto glview = cocos2d::Director::getInstance()->getOpenGLView(); glview->handleTouchesCancel(i, (intptr_t*)ids, xs, ys); }
最終,在GLView中完成給事件調度器發送相應的EventTouch事件。這裏單點觸摸跟多點觸摸處理差異不是很大。
auto listener = EventListenerTouchAllAtOnce::create(); listener->onTouchesBegan = CC_CALLBACK_2(Test::onTouchesBegan, this); listener->onTouchesMoved = CC_CALLBACK_2(Test::onTouchesMoved, this); listener->onTouchesEnded = CC_CALLBACK_2(Test::onTouchesEnded, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
技術交流QQ羣:528655025
做者:AlphaGL
出處:http://www.cnblogs.com/alphagl/ 版權全部,歡迎保留原文連接進行轉載 :)