tutorial swipeable - oxygine/oxygine-framework GitHub Wiki

Let's create a Swipeable component

Beginning

The function of this component is simple: move a mouse or finger over the screen from left to right or vice-versa to flip through different screens.

Start with the basics, then increase complexity as we go. I have readied a few images for the purpose of testing

Let's use the example in the oxygine-framework/examples/HelloWorld folder as a template for development. Open the project in your preferred IDE. Delete everything we don't need from example.cpp, leaving only the main functions and resource loading. For simplicity, all of the code will be edited and added only in this file.

#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();
}

https://github.com/oxygine/oxygine-tutorial-swipeable/blob/3ae2aa5815311e6846a2514e282a079b42d27369/src/example.cpp

We can compile and run, which will show an empty black screen.

Open the resource file and add our images there. Don't forget to copy the images to the data/images folder.

<?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>

https://github.com/oxygine/oxygine-tutorial-swipeable/blob/fc54d35cf41133786c623aaa95cb5279dc1c7a28/data/res.xml

Now, let's begin. First off, create the first image on the screen (img1.png). For this, we create a new Actor of type [Sprite]. (http://oxygine.org/doc/api/classoxygine_1_1_sprite.html) and attach it to the topmost element of our stage Stage.

There exists only one Stage, which can be retrieved using the function getStage()

The Stage is created and configured in 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());
}

Compile and run.

Now, let's implement the touch functionality. For now, we create two EventListeners for handling touching and letting go of the screen with the finger/mouse:

sprite->addEventListener(TouchEvent::TOUCH_DOWN, [=](Event*) {
    logs::messageln("touch down");
});

sprite->addEventListener(TouchEvent::TOUCH_UP, [=](Event*) {
    logs::messageln("touch up");
});

If you run the application now, then you will see the following in the output window:

We can add this for testing:

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);
});

https://github.com/oxygine/oxygine-tutorial-swipeable/commit/02b49bf2e44b2677440023588ec4f68ec70afa76

Now, upon pressing (event TOUCH_DOWN) our sprite goes to the edge of the screen to X = 500 in 500 milliseconds, and upon letting go (event TOUCH_UP) returns to the original position X = 0 in 1000 milliseconds. Moving the sprite is handled by Oxygine's built-in Tweening functionality.

Unlike the event TOUCH_DOWN, which is triggered only when the sprite is clicked, the event TOUCH_UP can be triggered even if the mouse/finger is off of the sprite (but only in the case that it has been previously pressed).

You can read more about events here.

Now, let's handle the actual Swipe. We will need TouchEvent::MOVE for this. Let's see what happens:

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");
        }
    });
}

Changes:

  1. I commented out the old code from addTween.
  2. Added 2 variables.
  3. In TOUCH_DOWN handling, we remember that the screen has been touched/mouse pressed and the finger's/mouse's coordinates.
  4. In MOVE handling, note how much the finger/mouse has moved from its original position. If it has been moved at least 50 pixels on the X-axis, then we count it as a Swipe and log it.

Now we add the visual movement of the image on the screen:

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);
        }
});

https://github.com/oxygine/oxygine-tutorial-swipeable/commit/a9c47193cf7922c6616606248792d64d3bc1a781

We run the application, perform the first Swipe and notice a problem: we can't swipe anymore. Why? Because our sprite went beyond the border of our screen and we are no longer able to touch it with the mouse/finger. How do we solve this? Well, we need to make the Swipe event independent of the position of our sprite. To do so, we move our EventListeners from the sprite onto the Stage:

getStage()->addEventListener(TouchEvent::TOUCH_DOWN, ...
getStage()->addEventListener(TouchEvent::TOUCH_UP, ...
getStage()->addEventListener(TouchEvent::MOVE, …

https://github.com/oxygine/oxygine-tutorial-swipeable/commit/83af0f97b41cd86a5d9e1b8c3568ec99e2979239

Much better. Additionally, we solved another problem that is caused by “touch->localPosition” being dependant on the coordinates of the sprite during its movement, which can lead to all kinds of crazy behaviour if the Swipe is done very quickly. Now, touch->localPosition is attached to the Stage which, unlike our sprite, stays in the same position.

The basic implementation is now complete. All we have left to do is to add consecutive sprites and move them correctly when the screen is swiped.

Now we add more code to handle swiping to the left (swiping to the right is very similar in implementation, so I won't do it here for the sake of simplicity):

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;
        }

https://github.com/oxygine/oxygine-tutorial-swipeable/commit/f313068fe984ee4d8c801692da3dd3bbcfa80fbd

Changes:

  1. Notice the new variables.
  2. The sprite is now stored globally so that we can change it.
  3. Added an array of image names and the current index.

The code is not without issues, however. The most notable issue being that upon very quick swipes, sprites are created too early, but we won't pay attention to that for the meantime.

We have a leak!!! sprite = nextSprite; We forgot about the previous sprite. Even if it hasn't gone anywhere it remains on the Stage, although we can no longer see it. Go into the Tree Inspector, and it will show the entire hierarchy of all created objects on the Stage:

On the screen we can see the green image, and the first 3 are located at (-960, 0) beyond the screen.

Let's remove them. Find the line:

sprite->addTween(Actor::TweenX(-sprite->getWidth()), 300);

And detach the actor:

spTween tween = sprite->addTween(Actor::TweenX(-sprite->getWidth()), 300);
tween->detachWhenDone();

Upon the completion of the animation, the sprite will be detached from its parent. Considering that (almost) all objects in oxygine are stored as smart pointers, the detached sprite will be automatically deleted from memory!

https://github.com/oxygine/oxygine-tutorial-swipeable/commit/d488d7f53f16016e538800ed82b9f3919dde6e08

We run the application, perform a couple of swipes to the left and ensure that the stage only has a single sprite - the one we currently see on the screen:

Conclusion

We have implemented the basic functionality of Swipe and can now swipe across the screen to see different images. What are we still missing?

  1. We need to decouple this code and make it a separate component so that we can reuse it in other projects.
  2. It doesn't function properly when the screen is swiped very quickly.
  3. Currently only supports the resolution it's implemented for.
  4. The ability to have the sprites in a separate window in the application rather than the entire screen (we will need to clip the sprites that are beyond the window, ClipRectActor might be useful here).
⚠️ **GitHub.com Fallback** ⚠️