Cocos2dx源碼賞析(3)之事件分發
這篇,繼續從源碼的角度賞析下Cocos2dx引擎的另一模塊事件分發處理機制。引擎的版本是3.14。同時,也是學習總結的過程,希望通過這種方式來加深對Cocos2dx引擎的理解。如有理解錯誤的地方,還望不吝賜教。
作者:AlphaGL。版權所有,歡迎保留原文鏈接進行轉載 😃
傳送門:
Cocos2dx源碼賞析(1)之啟動流程與主循環
Cocos2dx源碼賞析(2)之渲染
1、事件與監聽
在Cocos2dx中,Event類是所有事件的基類,也是對各種事件的統一的抽象。事件的類型有如下:
- EventAcceleration(加速度事件)
- EventController(游戲手柄事件)
- EventCustom(自定義事件)
- EventFocus(焦點事件)
- EventKeyboard(鍵盤事件)
- EventMouse(鼠標事件)
- EventTouch(觸摸事件)
有上面各種不同類型的事件,就有處理對應事件的監聽器。EventListener是所有事件監聽的基類。
(圖片出自Cocos2d-x官方文檔)
以上是事件監聽器的繼承關系。對應的事件監聽器如下:
- EventListenerAcceleration
- EventListenerController
- EventListenerCustom
- EventListenerFocus
- EventListenerKeyboard
- EventListenerMouse
- EventListenerTouchOneByOne(單點觸摸)、EventListenerTouchAllAtOnce(多點觸摸)
2、事件調度器EventDispatcher
在Cocos2dx中由事件調度器EventDispatcher來統一分發事件類型,並觸發相應的事件監聽器。通過,上一篇對Cocos2dx渲染過程的分析中,可知在導演類Director方法中會初始化事件監聽器EventDispatcher,繪制場景過程中,會響應相應的自定義事件。
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是單例的,所以,我們總是能獲得全局的事件調度器,來注冊相應的事件監聽。
那么,按照調用的順序依次介紹下,以上自定義事件:
_eventBeforeUpdate:在定時器Scheduler執行update之前,事件調度器EventDispatcher會調度執行該類型的事件,即EventCustom自定義類型事件。
_eventAfterUpdate:在定時器Scheduler執行update之后,事件調度器EventDispatcher會調度執行該類型的事件,即EventCustom自定義類型事件。
_eventAfterVisit:在遍歷場景之后會執行該事件。
_eventAfterDraw:在渲染之后會執行該事件。
而最終都會調到EventDispatcher的dispatchEvent方法中去:
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方法。
3、加速度事件EventAcceleration
這里,先來看看Cocos2dx中如何對加速度事件以及相應的監聽做封裝的。以Android和iOS平台為例,來結合源碼來說明下,整個事件的相應過程。
在手機中,會內置一些傳感器來探測外界的信號,並將探測到的數據轉換成相應的數值信息來通過手機內的應用,來做出相應的適應變化。例如有:加速度傳感器、壓力傳感器、溫度傳感器等。
3.1 Android
在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整個的響應過程。
3.2 iOS
在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類型的事件,並且加速度傳感器獲取到的數據傳給了引擎。
3.3 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*/)
{
}
4、游戲手柄事件EventController
Cocos2dx也提供了對游戲手柄的支持。同樣,分別以Android和iOS為例,來結合源碼來說明下,整個事件的相應過程。
4.1 Android
在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發送對應的事件。便完成了從事件從觸發到執行的過程。
4.2 iOS
在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中對應的方法,這里就不再贅述了。
4.3 EventController應用
_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();
5、自定義事件EventCustom
自定義事件是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();
});
6、焦點事件EventFocus與鍵盤事件EventKeyboard
因為,焦點事件是在接受到按鍵事件時,來處理獲得焦點還是失去焦點的。所以,這兩個事件放在一起介紹。
6.1 Android
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;
}}
6.2 iOS
由於iOS並不支持EventKeyboard監聽。所以,在iOS平台下,注冊了EventListenerKeyboard監聽,但並沒有發送EventKeyboard事件,因此,主循環並不會執行相應的回調(onKeyPressed、onKeyReleased等)事件。
6.3 EventKeyboard應用
auto listener = EventListenerKeyboard::create();
listener->onKeyPressed = [](EventKeyboard::KeyCode keyCode, Event* event){
};
listener->onKeyReleased = [](EventKeyboard::KeyCode keyCode, Event* event){
};
6.4 EventFoucus
焦點事件主要是在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);
}
}
7、 觸摸事件EventTouch
觸摸事件也得分Android和iOS來談,這些也同樣是通過物理接口在相應的平台上進行捕獲來傳遞到Cocos2dx引擎中。
7.1 Android
在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發送對應的事件。便完成了從事件從觸發到執行的過程。
7.2 iOS
- (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事件。這里單點觸摸跟多點觸摸處理差別不是很大。
7.3 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/
版權所有,歡迎保留原文鏈接進行轉載 😃