Cocos2dx源碼賞析(3)之事件分發

Cocos2dx源碼賞析(3)之事件分發

這篇,繼續從源碼的角度賞析下Cocos2dx引擎的另外一模塊事件分發處理機制。引擎的版本是3.14。同時,也是學習總結的過程,但願經過這種方式來加深對Cocos2dx引擎的理解。若有理解錯誤的地方,還望不吝賜教。html

做者:AlphaGL。版權全部,歡迎保留原文連接進行轉載 :)java

傳送門:
Cocos2dx源碼賞析(1)之啓動流程與主循環
Cocos2dx源碼賞析(2)之渲染android

一、事件與監聽

在Cocos2dx中,Event類是全部事件的基類,也是對各類事件的統一的抽象。事件的類型有以下:ios

  • EventAcceleration(加速度事件)
  • EventController(遊戲手柄事件)
  • EventCustom(自定義事件)
  • EventFocus(焦點事件)
  • EventKeyboard(鍵盤事件)
  • EventMouse(鼠標事件)
  • EventTouch(觸摸事件)

有上面各類不一樣類型的事件,就有處理對應事件的監聽器。EventListener是全部事件監聽的基類。

(圖片出自Cocos2d-x官方文檔)app

以上是事件監聽器的繼承關係。對應的事件監聽器以下:框架

  • EventListenerAcceleration
  • EventListenerController
  • EventListenerCustom
  • EventListenerFocus
  • EventListenerKeyboard
  • EventListenerMouse
  • EventListenerTouchOneByOne(單點觸摸)、EventListenerTouchAllAtOnce(多點觸摸)

二、事件調度器EventDispatcher

在Cocos2dx中由事件調度器EventDispatcher來統一分發事件類型,並觸發相應的事件監聽器。經過,上一篇對Cocos2dx渲染過程的分析中,可知在導演類Director方法中會初始化事件監聽器EventDispatcher,繪製場景過程當中,會響應相應的自定義事件。less

bool Director::init(void) {
    _eventDispatcher = new (std::nothrow) EventDispatcher();
    _eventAfterDraw = new (std::nothrow) EventCustom(EVENT_AFTER_DRAW);
    _eventAfterDraw->setUserData(this);
    _eventAfterVisit = new (std::nothrow) EventCustom(EVENT_AFTER_VISIT);
    _eventAfterVisit->setUserData(this);
    _eventBeforeUpdate = new (std::nothrow) EventCustom(EVENT_BEFORE_UPDATE);
    _eventBeforeUpdate->setUserData(this);
    _eventAfterUpdate = new (std::nothrow) EventCustom(EVENT_AFTER_UPDATE);
    _eventAfterUpdate->setUserData(this);
    _eventProjectionChanged = new (std::nothrow) EventCustom(EVENT_PROJECTION_CHANGED);
    _eventProjectionChanged->setUserData(this);
    _eventResetDirector = new (std::nothrow) EventCustom(EVENT_RESET);
}

在Director導演類的init方法中,初始化了事件調度器_eventDispatcher,而Director是單例的,因此,咱們老是能得到全局的事件調度器,來註冊相應的事件監聽。ide

那麼,按照調用的順序依次介紹下,以上自定義事件:
_eventBeforeUpdate:在定時器Scheduler執行update以前,事件調度器EventDispatcher會調度執行該類型的事件,即EventCustom自定義類型事件。
_eventAfterUpdate:在定時器Scheduler執行update以後,事件調度器EventDispatcher會調度執行該類型的事件,即EventCustom自定義類型事件。
_eventAfterVisit:在遍歷場景以後會執行該事件。
_eventAfterDraw:在渲染以後會執行該事件。學習

而最終都會調到EventDispatcher的dispatchEvent方法中去:ui

void EventDispatcher::dispatchEvent(Event* event)
{
    if (!_isEnabled)
        return;
    
    updateDirtyFlagForSceneGraph();
    
    
    DispatchGuard guard(_inDispatch);
    
    if (event->getType() == Event::Type::TOUCH)
    {
        dispatchTouchEvent(static_cast<EventTouch*>(event));
        return;
    }
    
    auto listenerID = __getListenerID(event);
    
    sortEventListeners(listenerID);
    
    auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners;
    if (event->getType() == Event::Type::MOUSE) {
        pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners;
    }
    auto iter = _listenerMap.find(listenerID);
    if (iter != _listenerMap.end())
    {
        auto listeners = iter->second;
        
        auto onEvent = [&event](EventListener* listener) -> bool{
            event->setCurrentTarget(listener->getAssociatedNode());
            listener->_onEvent(event);
            return event->isStopped();
        };
        
        (this->*pfnDispatchEventToListeners)(listeners, onEvent);
    }
    
    updateListeners(event);
}

在dispatchEvent中,會從從保存了以類型id爲key的Map中來遍歷註冊的監聽,並執行該監聽的callback方法。

三、加速度事件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*/)
{

}

四、遊戲手柄事件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();

五、自定義事件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();
    });

六、焦點事件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);
    }

}

七、 觸摸事件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/ 版權全部,歡迎保留原文連接進行轉載 :)

相關文章
相關標籤/搜索