От переводчика


Доброго времени суток! Эта статья представляет собой перевод документации к движку Cocos2d-x.

В предыдущих частях мы уже рассмотрели большинство основных компонентов движка:

Sprite
Action
UI Components
Scene и другие

Нам осталось совсем немного для создания полноценной игры. А именно, обеспечить сам игровой процесс. Для этого, в Cocos2d-x существует диспетчер событий.

Диспетчер событий


Что такое механизм EventDispatch? EventDispatch — это механизм реагирования на пользовательские события.

Основы:

  • Слушатели событий инкапсулируют ваш код обработки событий.
  • Диспетчер событий уведомляет слушателей о пользовательских событиях.
  • Объекты событий содержат информацию о событии.

5 типов слушателей событий


EventListenerTouch — реагирует на касание сенсорного экрана

EventListenerKeyboard — реагирует на нажатия клавиатуры

EventListenerAcceleration — реагирует на события акселерометра

EventListenMouse — реагирует на события мышки

EventListenerCustom — реагирует на настраиваемые события

FixedPriority vs SceneGraphPriority


EventDispatcher использует приоритеты, чтобы решать, какие слушатели получат событие первыми.

Fixed Priority(фиксированный приоритет) представляет целочисленное значение. Слушатели событий с более низким значение приоритета получают события на обработку, раньше, чем слушатели с высоким значением приоритета.

Scene Graph Priority — это указатель на Node-объект. Слушатели событий, чьи узлы имеют более высокое значение z-прядка(которые рисуются сверху), получают события прежде, чем слушатели, чьи узлы имеют более низкие значения z-порядка(которые рисуются снизу). Это гарантирует, что события касания получат передние элементы, как и следовало ожидать.

Помните граф сцены? Когда мы говорили об этой диаграмме?

image

При использовании Scene Graph Priority вы фактически проходите это дерево в обратную сторону… I, H, G, F, E, D, C, B, A. Если сработает событие, H заметит это и либо поглотит его (подробнее об этом ниже), либо пропустит дальше к I. Тоже самое с I, он либо потребит действие, либо пропустит его дальше к G, и так далее.

События касания


События касания — самые важные события в мобильном гейминге. Они легки в создании и предоставляют гибкую функциональность. Давайте уточним, что такое событие касания. Когда вы дотрагиваетесь до экрана вашего мобильного девайса, он воспринимает касание, смотрит где вы прикоснулись и решает, на что вы нажали. Затем, вы получаете ответ. Возможно, вы коснулись не активного объекта, а чего-то под ним. Событиям касания, обычно, присваивается приоритет, а отвечает событие с наивысшим приоритетом.

Как создать базовый слушатель события касания:

//  Создание "одиночного" слушателя событий касания
// (обрабатывается одно касание за раз)
auto listener1 = EventListenerTouchOneByOne::create();

// срабатывает при нажатии
listener1->onTouchBegan = [](Touch* touch, Event* event){
    // ваш код
    return true; // Если вы его приняли
};

// срабатывает при перемещении касания
listener1->onTouchMoved = [](Touch* touch, Event* event){
    // ваш код
};

// срабатывает при отпускании
listener1->onTouchEnded = [=](Touch* touch, Event* event){
    // ваш код
};

// Добавляем слушатель
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

Как вы могли заметить, существуют 3 отдельных действия, которые вы можете использовать для слушателя касаний. Каждое из них вызывается при определенных условиях.

onTouchBegan — срабатывает при нажатии.

onTouchMoved — срабатывает при перемещении касания.

onTouchEnded — срабатывает при отпускании.

Поглощение событий


Когда у вас есть слушатель и вы хотите чтобы объект принял поданное событие, вы должны поглотить его. Другими словами, вы принимаете его, чтобы оно не прошло дальше, к другим объектам с более низким приоритетом. Это легко реализовать.

// Чтобы "поглотить" действие, возвращаем true
// Метод onTouchBegan поглотит действие касания,
// не позволяя другим слушателям использовать его
listener1->setSwallowTouches(true);

// Также вы должны вернуть true в onTouchBegan()

listener1->onTouchBegan = [](Touch* touch, Event* event){
    // ваш код

    return true;
};


Создание событий клавиатуры


Для компьютерных игр, вам может понадобится использование клавиатуры. Cocos2d-x поддерживает клавиатуру. Как и события касания, клавиатурные события легко создаются.

// создание слушателя клавиатурных событий
auto listener = EventListenerKeyboard::create();
listener->onKeyPressed = CC_CALLBACK_2(KeyboardTest::onKeyPressed, this);
listener->onKeyReleased = CC_CALLBACK_2(KeyboardTest::onKeyReleased, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

// реализация прототипа callback функции
void KeyboardTest::onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event)
{
        log("Key with keycode %d pressed", keyCode);
}

void KeyboardTest::onKeyReleased(EventKeyboard::KeyCode keyCode, Event* event)
{
        log("Key with keycode %d released", keyCode);
}

Создание событий акселерометра


Некоторые мобильные устройства оснащены акселерометром. Акселерометр — это датчик, который измеряет перегрузку, а также изменения направления. Используется, например, когда необходимо перемещать телефон из стороны в сторону, для имитации балансирующего действия. Cocos2d-x также поддерживает эти события. Перед использованием событий акселерометра, вам необходимо подключить их.

Device::setAccelerometerEnabled(true);

// создание слушателя
auto listener = EventListenerAcceleration::create(CC_CALLBACK_2(
AccelerometerTest::onAcceleration, this));

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

// реализация прототипа callback функции
void AccelerometerTest::onAcceleration(Acceleration* acc, Event* event)
{
    //  Processing logic here
}

Создание событий мыши


Как всегда, Cococ2d-x поддерживает события мыши.

_mouseListener = EventListenerMouse::create();
_mouseListener->onMouseMove = CC_CALLBACK_1(MouseTest::onMouseMove, this);
_mouseListener->onMouseUp = CC_CALLBACK_1(MouseTest::onMouseUp, this);
_mouseListener->onMouseDown = CC_CALLBACK_1(MouseTest::onMouseDown, this);
_mouseListener->onMouseScroll = CC_CALLBACK_1(MouseTest::onMouseScroll, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(_mouseListener, this);

void MouseTest::onMouseDown(Event *event)
{
    // для демонстрации события...
    EventMouse* e = (EventMouse*)event;
    string str = "Mouse Down detected, Key: ";
    str += tostr(e->getMouseButton());
}

void MouseTest::onMouseUp(Event *event)
{
    // для демонстрации события...
    EventMouse* e = (EventMouse*)event;
    string str = "Mouse Up detected, Key: ";
    str += tostr(e->getMouseButton());
}

void MouseTest::onMouseMove(Event *event)
{
    // для демонстрации события...
    EventMouse* e = (EventMouse*)event;
    string str = "MousePosition X:";
    str = str + tostr(e->getCursorX()) + " Y:" + tostr(e->getCursorY());
}

void MouseTest::onMouseScroll(Event *event)
{
    // для демонстрации события...
    EventMouse* e = (EventMouse*)event;
    string str = "Mouse Scroll detected, X: ";
    str = str + tostr(e->getScrollX()) + " Y: " + tostr(e->getScrollY());
}

Создание пользовательских событий


Типы событий, описанных выше, определены системой, эти события (такие как касание экрана, нажатие клавиши и др.) обрабатываются системой автоматически. К тому же, вы можете сделать свое собственное событие, которое будет обрабатываться не системой, а вашим кодом.

_listener = EventListenerCustom::create("game_custom_event1", [=](EventCustom* event){
    std::string str("Custom event 1 received, ");
    char* buf = static_cast<char*>(event->getUserData());
    str += buf;
    str += " times";
    statusLabel->setString(str.c_str());
});

_eventDispatcher->addEventListenerWithFixedPriority(_listener, 1);

Слушатель пользовательского события был определен с методом реакции и добавлен к диспетчеру событий. Как будет вызываться пользовательское событие? Смотрите далее:

static int count = 0;
++count;

char* buf[10];
sprintf(buf, "%d", count);

EventCustom event("game_custom_event1");
event.setUserData(buf);

_eventDispatcher->dispatchEvent(&event);

В примере выше, мы создали объект EventCustom и задали значение UserData. Затем, событие вызывается вручную, с помощью _eventDispatcher->dispatchEvent(&event). Это запускает событие, определенное ранее. Обработчик событий вызывается непосредственно, поэтому в качестве параметра UserData, может использоваться локальная переменная стека.

Регистрация событий в диспетчере


Зарегистрировать событие просто. Вот пример для слушателя событий касания:

// Добавляем слушателя
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1,
sprite1);

Важно отметить, что событие касания может быть зарегистрировано только на один объект. Если вам необходимо использовать один слушатель для множества объектов, для этого необходимо использовать clone().

// Добавляем слушателя
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1,
sprite1);

// Добавляем того же слушателя к другому объекту
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(),
 sprite2);

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(),
 sprite3);

Удаление событий из диспетчера


Мы можем его удалить из диспетчера, следующим методом:

_eventDispatcher->removeEventListener(listener);

Хотя они могут казаться особенными, встроенные Node-объекты используют диспетчер событий тем же самым методом, о котором мы говорили. Имеет смысл, не так ли? Возьмем Menu для примера. Когда вы кликаете по элементам меню, вызывается событие. Слушатели Node-объектов также просто удалить.

От переводчика


Вот мы и закончили с основными компонентами движка! Следующие на очереди главы, в основном, очень короткие, а большую часть в них занимает один лишь пример кода. Я считаю, что неуместно будет выкладывать их по одной на Хабр. Совесть мне этого не позволит. Миксовать темы тоже не очень хорошая идея.

Но все же, я планирую опубликовать еще сколько-нибудь уроков, которые уже не будут чистыми переводами. Ниже я оставлю опрос, чтобы узнать что вам будет интересно. Также, можете предлагать свои варианты в комментариях.

Комментарии (6)


  1. heximal
    03.11.2017 19:56

    Проголосовал за статью про оптимизацию. Очень хотелось бы послушать совет.
    Никак не могу победить рывки при рендеринге движения сцены (платформер, где игрок перемещается снизу вверх). Они происходят не часто, может пару раз за игровую сессию, а может и вообще нет.
    Перепробовал все, что нашел по данной теме касательно cocos2d.
    Не могу сказать, что совсем в край критично дергается, но внутренний перфекционист никак не угомонится.
    Есть подозрение, что это связано с динамическим созданием спрайтов.
    Интересно, можно ли это запихать в отдельный поток?

    PS. Использую Javascript скриптинг.


    1. MrMysterious Автор
      03.11.2017 22:57

      Не совсем понимаю, что имеется ввиду под динамическим созданием спрайтов. Но в любом случае, чтобы понять в чем суть торможений нужно видеть код игры.
      Можно ли их запихать в отдельный поток? Скорее всего можно, но я не уверен что это поможет и как-то снизит нагрузку. Я бы даже не рассматривал этот вариант(но может я ошибаюсь).


      1. heximal
        05.11.2017 12:22

        я имею в виду операции типа new cc.Sprite(textureName), они ведь не сказать, что легковесные.
        текстуры все форматирую в четырехбитный pvr.ccz, и все равно подозрение, что создание страйтов тормозит.
        попробую расставить тайминги.


  1. saw_tooth
    03.11.2017 20:03

    Спасибо за статью. Очень познавательно.
    А не планируется освещение дополнительного инструментария для работы с данным движком? Ну, может какие редакторы анимаций, или может тайл маперы ( один уже знаем TileMapEditor) в таком роде?


    1. MrMysterious Автор
      03.11.2017 22:30

      Рад что вам понравилось! Я сам все еще в процессе освоения этих инструментов. Если вам это интересно, то я мог бы написать по ним статью, как только наберу достаточно опыта.


  1. saw_tooth
    05.11.2017 18:04

    Вот да, будет очень не плохо.
    ЗЫ. Даже касаемо Tiled, там есть некоторые, не очевидные для начинающих, вещи (работа с объектами, анимациями, перенос переменных). Буду премного благодарен.