tutorial swipeable_ru - oxygine/oxygine-framework GitHub Wiki
Суть его работы проста: двигаем по экрану пальцем или мышкой слева направо или справа налево и листаем страницы.
Начнем с простого, постепенно усложняя код. Я подготовил несколько картинок, которые будут использоваться
За основу для разработки берем пример из папки oxygine-framework/examples/HelloWorld. Открываем проект в вашей любимой IDE. Из файла example.cpp удаляем все лишнее, оставляя только основные функции и загрузку ресурсов. Для простоты и наглядности весь код будет редактироваться и добавляться только в нем.
#include "oxygine-framework.h"
using namespace oxygine;
//it is our resources
//in real project you would have more than one Resources declarations.
//It is important on mobile devices with limited memory and you would load/unload them
Resources gameResources;
void example_preinit() {}
//called from main.cpp
void example_init()
{
//load xml file with resources definition
gameResources.loadXML("res.xml");
}
//called each frame from main.cpp
void example_update()
{
}
//called each frame from main.cpp
void example_destroy()
{
//free previously loaded resources
gameResources.free();
}
Можно скомпилировать, запустить и увидим пустой черный экран.
Откроем файл с ресурсами и добавим туда наши картинки, не забыв скопировать их в папку data/images.
<?xml version="1.0"?>
<resources>
<set path = "images" />
<atlas>
<image file="img1.png" />
<image file="img2.png" />
<image file="img3.png" />
<image file="img4.png" />
</atlas>
</resources>
А теперь приступим. Первым делом выведем на экране первую картинку img1.png. Для этого создаем Actor типа Sprite и прицепляем ее к самому верхнему элементу нашей сцены Stage.
Actor типа Stage существует только в одном экземпляре и может быть получен в с помощью функции getStage()
Кстати, сам Stage создается и настраивается в main.cpp
void example_init()
{
//load xml file with resources definition
gameResources.loadXML("res.xml");
spSprite sprite = new Sprite;
sprite->setResAnim(gameResources.getResAnim("img1"));
sprite->attachTo(getStage());
}
Компилируем и запускаем.
Переходим к обработке касаний. Добавим пустые заглушки для обработки событий прикосновения и отпускания пальца:
sprite->addEventListener(TouchEvent::TOUCH_DOWN, [=](Event*) {
logs::messageln("touch down");
});
sprite->addEventListener(TouchEvent::TOUCH_UP, [=](Event*) {
logs::messageln("touch up");
});
Если сейчас запустить приложение, то можно понаблюдать за выводом в консоль/output окно:
Для теста можно попробовать такое:
sprite->addEventListener(TouchEvent::TOUCH_DOWN, [=](Event*) {
logs::messageln("touch down");
sprite->addTween(Actor::TweenX(500), 500);
});
sprite->addEventListener(TouchEvent::TOUCH_UP, [=](Event*) {
logs::messageln("touch up");
sprite->addTween(Actor::TweenX(0), 1000);
});
То есть при нажатии (событие TOUCH_DOWN) наш спрайт уезжает к краю экрана в точку 500 по оси X за 500 миллисекунд, а при отпускании (событие TOUCH_UP) возвращается обратно в точку 0 за 1000 миллисекунд. Перемещение спрайта производится с помощью встроенной в oxygine функциональности Tweening.
В отличии от события TOUCH_DOWN, которое срабатывает только при попадании по спрайту, событие TOUCH_UP может сработать, даже если касание было сделано мимо спрайта (но только в том случае если он был зажат перед этим).
Подробнее изучить обработку событий можно тут.
Попытаемся теперь отследить событие Swipe. Нам понадобится дополнительно обработчик движения мыши TouchEvent::MOVE. Смотрим что получилось:
bool pressed = false;
Vector2 downPos(0,0);
void example_init()
{
... //skipped some code
sprite->addEventListener(TouchEvent::TOUCH_DOWN, [=](Event* ev) {
logs::messageln("touch down");
TouchEvent *touch = (TouchEvent*)ev;
downPos = touch->localPosition;
pressed = true;
});
sprite->addEventListener(TouchEvent::TOUCH_UP, [=](Event*) {
logs::messageln("touch up");
//sprite->addTween(Actor::TweenX(0), 1000);
pressed = false;
});
sprite->addEventListener(TouchEvent::MOVE, [=](Event* ev) {
if (!pressed)
return;
TouchEvent *touch = (TouchEvent*)ev;
Vector2 dir = touch->localPosition - downPos;
if (dir.x < -50)
{
pressed = false;
logs::messageln("swipe left");
}
if (dir.x > 50)
{
pressed = false;
logs::messageln("swipe right");
}
});
}
Изменения:
- Я закомментировал старый код с addTween.
- Добавил 2 переменные.
- В TOUCH_DOWN обработчике запоминаем, что палец был нажат и его координаты.
- В MOVE обработчике смотрим на сколько сдвинулся палец относительно сохраненной координаты, если по X преодолен порог в 50 пикселей, то засчитываем как Swipe и выводим сообщение в лог.
Идем дальше, добавляем визуальное движение картинки по экрану:
sprite->addEventListener(TouchEvent::MOVE, [=](Event* ev) {
if (!pressed)
return;
TouchEvent *touch = (TouchEvent*)ev;
Vector2 dir = touch->localPosition - downPos;
if (dir.x < -50)
{
pressed = false;
logs::messageln("swipe left");
sprite->addTween(Actor::TweenX(sprite->getX() - sprite->getWidth()), 300);
}
if (dir.x > 50)
{
pressed = false;
logs::messageln("swipe right");
sprite->addTween(Actor::TweenX(sprite->getX() + sprite->getWidth()), 300);
}
});
Запускаем, делаем первый Swipe и натыкаемся на проблему: повторные swipe не происходят. Почему? Потому что наш sprite уехал за границы экрана и мы больше не можем по нему попасть мышкой. Как это решить? Если подумать, то нам не важно какого он размера и куда он уезжает, нас интересует только само событие Swipe на экране. Потому перевешиваем наши EventListeners со sprite на Stage:
getStage()->addEventListener(TouchEvent::TOUCH_DOWN, ...
getStage()->addEventListener(TouchEvent::TOUCH_UP, ...
getStage()->addEventListener(TouchEvent::MOVE, …
Стало лучше. Одновременно с этим мы решили проблему, когда “touch->localPosition” зависит от координат sprite и меняется во время его движения, что приводит к полному хаосу, если делать Swipe очень быстро. Теперь же touch->localPosition привязан к системе координат Stage, которая статична в отличии от sprite.
И вот у нас готова базовая функциональность. Дальше дело техники, создавать последовательно другие картинки по мере Swipe и двигать их.
Дорабатываем наш блок кода, отвечающий за swipe left (swipe right не привожу, чтобы не загромождать, он принципиально ничем не отличается):
int current = 0;
spSprite sprite;
const char* images[] = {"img1", "img2", "img3", "img4"};
void example_init()
{
//load xml file with resources definition
gameResources.loadXML("res.xml");
sprite = new Sprite;
... //skipped some code
if (dir.x < -50)
{
pressed = false;
logs::messageln("swipe left");
sprite->addTween(Actor::TweenX(-sprite->getWidth()), 300);
current = (current + 1) % 4;
spSprite nextSprite = new Sprite;
nextSprite->setResAnim(gameResources.getResAnim(images[current]));
nextSprite->setX(sprite->getWidth());
nextSprite->addTween(Actor::TweenX(0), 300);
nextSprite->attachTo(getStage());
sprite = nextSprite;
}
Изменения:
- Обратите внимание на новые переменные.
- Объект sprite теперь хранится глобально, чтоб мы могли его переназначить.
- Добавили массив из списка картинок и индекс текущей.
Данный код не лишен недостатков, самое очевидное - при быстрых Swipeспрайты будут создаваться преждевременно, но пока мы не будем обращать на это внимание.
У нас есть утечка!!! sprite = nextSprite; Мы забыли про старый спрайт, но он никуда не делся, он продолжает висеть в Stage, даже если его не видно, полистайте пару раз и загляните в Tree Inspector. Он отображает полную иерархию всех созданных объектов в Stage:
На экране сейчас зеленая картинка, а первые 3 где-то висят в позиции (-960,0) за пределами видимости.
Избавляемся от них. Строку в swipe left:
sprite->addTween(Actor::TweenX(-sprite->getWidth()), 300);
Дополняем до:
spTween tween = sprite->addTween(Actor::TweenX(-sprite->getWidth()), 300);
tween->detachWhenDone();
То есть по завершению анимации перемещения sprite будет автоматически detached от родителя. А так как все (почти все) объекты в oxygine унаследованы от счетчика ссылок и хранятся как smart pointers, то потерянный sprite автоматически будет deleted из памяти.
Запускаем пример, делаем пару раз swipe left и убеждаемся что теперь в Stage только 1 спрайт, тот который мы сейчас видим:
На данном этапе мы добились базовой функциональности Swipe и можем свободно листать по экрану пальцем картинки. Чего тут не хватает?
- Надо оформить этот код как отдельный компонент, с возможностью его последующего переиспользования.
- Нет корректной работы при быстрых Swipe.
- Нет поддержки произвольных размеров.
- Нужна возможность рисовать элементы в окошке, отсекая все что за пределами. (пригодится ClipRectActor).