Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Make a sequence of QWidget moves



  • Hi everyone! I'm making a game. There're a game field made of 64 squares (like chess) and a QPushButton with icon on it. Button supposed to move on field randomly. Problem is that when i use move() several times in a row it only displays last move:

    QPushButton->move(ax, ay);
    for(int i=0; i<10000; i++){} //for delay
    QPushButton->move(bx, by);
    for(int i=0; i<10000; i++){}
    QPushButton->move(cx, cy);
    

    only

    QPushButton->move(cx, cy);
    

    will be executed with no delay before. Just button will be on position cx, cy when i start a program.
    Same thing happens when i use QPropertyAnimation:

    QPropertyAnimation *animation = new QPropertyAnimation(ui->pushButton, "geometry");
    
        animation->setDuration(10000);
        animation->setStartValue(QRect(20, 20, 80, 80));
        animation->setEndValue(QRect(100, 20, 80, 80));
        animation->start();
    
        animation->setDuration(10000);
        animation->setStartValue(QRect(100, 20, 80, 80));
        animation->setEndValue(QRect(100, 100, 80, 80));
        animation->start();
    
        animation->setDuration(10000);
        animation->setStartValue(QRect(100, 100, 80, 80));
        animation->setEndValue(QRect(20, 100, 80, 80));
        animation->start();
    

    Only last animation will be executed.
    How can i make as many moves as i need, so when i start the game all movements would be consistently executed with any needed delay?



  • @Peter_Dev

    For Loop as Delay is not a good idea because it can freeze your interface. Instead, you could use a QTimer to perform certain operation at intervals.

    For Example:

    QTimer *timer = new QTimer(this);
    timer->setInterval(10000);
    connect (timer, &QTimer::timeout, [] () {
        // execute
    });
    timer->start();
    

    Considering your problem, you could use a data struture (FIFO - First In, First Out) to store and recover your movements.

    QQueue

    struct Move{
      int x, y;
    }
    
    QQueue<Move> moveQueue;
    
    moveQueue.enqueue({1,1});
    moveQueue.enqueue({2,2});
    moveQueue.enqueue({1,2});
    moveQueue.enqueue({1,1});
    
    while (!moveQueue.isEmpty()){
        const Move& move = queue.dequeue();
        qDebug() << "X: " << move.x << " - Y:" << move.y;
    }
    

    Output:
    X: 1 - Y: 1
    X: 2 - Y: 2
    X: 1 - Y: 2
    X: 1 - Y: 1


    Using the example shown above, this solution comes:

    example.gif

    mainwindow.h file

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QQueue>
    
    namespace Ui {
    class MainWindow;
    }
    
    struct Move{ int x, y; };
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
    private:
        Ui::MainWindow *ui;
        QQueue<Move> moveQueue;
    };
    
    #endif // MAINWINDOW_H
    

    mainwindow.cpp

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    #include <QTimer>
    #include <QPropertyAnimation>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        moveQueue.enqueue({1,1});
        moveQueue.enqueue({2,2});
        moveQueue.enqueue({1,2});
        moveQueue.enqueue({1,1});
    
        QTimer *timer = new QTimer(this);
        timer->setInterval(10000);
    
        connect(timer, &QTimer::timeout, [this, timer](){
            if(moveQueue.empty()){
                timer->stop();
                return;
            }
    
            const Move& move = moveQueue.dequeue();
    
            QPropertyAnimation *animation = new QPropertyAnimation(ui->pushButton, "geometry");
            connect(animation, &QPropertyAnimation::finished, animation, &QPropertyAnimation::deleteLater); // auto destroy        
            animation->setDuration(1000); // 1s
            animation->setStartValue(ui->pushButton->geometry()); // actual geometry
            animation->setEndValue(QRect(80*move.x, 80*move.y, 80, 80)); // x = width * move.x | y = height * move.y
            animation->start();
        });
    
        timer->start(5000); // start after 5s
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    


  • @Peter_Dev

    For Loop as Delay is not a good idea because it can freeze your interface. Instead, you could use a QTimer to perform certain operation at intervals.

    For Example:

    QTimer *timer = new QTimer(this);
    timer->setInterval(10000);
    connect (timer, &QTimer::timeout, [] () {
        // execute
    });
    timer->start();
    

    Considering your problem, you could use a data struture (FIFO - First In, First Out) to store and recover your movements.

    QQueue

    struct Move{
      int x, y;
    }
    
    QQueue<Move> moveQueue;
    
    moveQueue.enqueue({1,1});
    moveQueue.enqueue({2,2});
    moveQueue.enqueue({1,2});
    moveQueue.enqueue({1,1});
    
    while (!moveQueue.isEmpty()){
        const Move& move = queue.dequeue();
        qDebug() << "X: " << move.x << " - Y:" << move.y;
    }
    

    Output:
    X: 1 - Y: 1
    X: 2 - Y: 2
    X: 1 - Y: 2
    X: 1 - Y: 1


    Using the example shown above, this solution comes:

    example.gif

    mainwindow.h file

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QQueue>
    
    namespace Ui {
    class MainWindow;
    }
    
    struct Move{ int x, y; };
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
    private:
        Ui::MainWindow *ui;
        QQueue<Move> moveQueue;
    };
    
    #endif // MAINWINDOW_H
    

    mainwindow.cpp

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    #include <QTimer>
    #include <QPropertyAnimation>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        moveQueue.enqueue({1,1});
        moveQueue.enqueue({2,2});
        moveQueue.enqueue({1,2});
        moveQueue.enqueue({1,1});
    
        QTimer *timer = new QTimer(this);
        timer->setInterval(10000);
    
        connect(timer, &QTimer::timeout, [this, timer](){
            if(moveQueue.empty()){
                timer->stop();
                return;
            }
    
            const Move& move = moveQueue.dequeue();
    
            QPropertyAnimation *animation = new QPropertyAnimation(ui->pushButton, "geometry");
            connect(animation, &QPropertyAnimation::finished, animation, &QPropertyAnimation::deleteLater); // auto destroy        
            animation->setDuration(1000); // 1s
            animation->setStartValue(ui->pushButton->geometry()); // actual geometry
            animation->setEndValue(QRect(80*move.x, 80*move.y, 80, 80)); // x = width * move.x | y = height * move.y
            animation->start();
        });
    
        timer->start(5000); // start after 5s
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    


  • @KillerSmath
    Thanks a lot for your help!
    Is there any reason behind this behavior, when only last move or animation will be displayed until i use stack?


  • Lifetime Qt Champion

    @Peter_Dev Loops and long lasting operation block event loop and as long as the event loop is blocked nothing will move. So, you move, then immediately block event loop, again move and block and so on - only last move will be actually visible.



  • @jsulm
    Ok, i understand.
    Thank you for explanation!


Log in to reply