Cocos2dx源码赏析(3)之事件分发

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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,518评论 25 707
  • Cocos2dx源码赏析(1)之启动流程与主循环 我们知道Cocos2dx是一款开源的跨平台游戏引擎,而学习开源项...
    AlphaGL阅读 3,684评论 2 11
  • 最近这段时间,刚刚升入初三,看到孩子们的状态真的替他们着急,所以用了教鞭教训孩子们,书写不好的作业不完成的,上课说...
    馨漪_a926阅读 251评论 0 0
  • 秀兰嫂子是个子矮小的二民哥从青海领回来的媳妇,我那时有十岁左右吧,着实被她的长相吓着了,中等的个子,胳膊腿都瘦的象...
    听雪1014阅读 735评论 0 0
  • 2017.3.29 晴 世界上没有丑女人,只有懒女人。 好多女人为了美丽,也选择高档的护肤品,美容院。但是琳琅满目...
    相爱相依娟阅读 779评论 0 3