Cocos2dx源码赏析(3)之事件分发
这篇,继续从源码的角度赏析下Cocos2dx引擎的另一模块事件分发处理机制。引擎的版本是3.14。同时,也是学习总结的过程,希望通过这种方式来加深对Cocos2dx引擎的理解。如有理解错误的地方,还望不吝赐教。
传送门:
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);