Smooth character movement



  • Hi everyone,

    This is my first post on the QT forums. I'm new to QT and fairly new to C++. I've mostly worked on web-based things using PHP, Javascript, etc. and some iOS work in Objective-C so please bear with me if I make a lot of mistakes with my coding and misuse of libraries here.

    I thought I'd try my hand at making a 2D RPG type game. I've followed some tutorials on YouTube to get me started but I'm having trouble making my character move across the screen smoothly. So to move the character left I use:

    void PlayerCharacter::keyPressEvent(QKeyEvent *event) 
    {
        if (event->key() == Qt::Key_A) {
                 const int STEP_SIZE = 1;
                 setPos(x()-STEP_SIZE,y());
        }
    }
    

    When the STEP_SIZE is small it looks smooh but the movement is slow. When the STEP_SIZE is larger (say 5), the movement is faster but watching the character move gives me a headache. Is there a better (proper) way to implement something like this so that the movement is smooth regardless of the speed?

    Thanks in advance for any help offered :)


  • Moderators

    Hi, welcome to devnet.

    You should not move your character in a key press event. Different keybords, OS settings, custom drivers etc. can all alter the frequency in which these happen. Even if you managed to fiddle with the values to look good on your computer it doesn't mean it would on others. For example your character might move at super speeds on a pro-gaming keyboard tuned for high-frequency updates.

    The correct way to do that is to "mark" your character as moving on key press and "unmark" it on key release. Then, in some timer or however you advance your game, you would move the characters that are marked. If the update rate is well synced with the refresh rate of the display you will get a smooth movement, independent of jittery key presses.



  • I see.

    Thanks Chris. I'll give that a go and see how far I get :)



  • Ok. So here's what I've come up with.

    void PlayerCharacter::keyPressEvent(QKeyEvent *event)
    {
        const int FPS = 60;
    
        // move player left
        if (event->key() == Qt::Key_A) {
            if (direction == directions::right) {
                // face character left
                setTransform(QTransform::fromScale(-1, 1));
                direction = directions::left;
            }
    
            if (!moveTimer->isActive()) {
                moveTimer->start(1000/FPS);
            }
        }
        // move player right
        else if (event->key() == Qt::Key_D) {
            if (direction == directions::left) {
                // face character right
                setTransform(QTransform::fromScale(1, 1));
                direction = directions::right;
            }
    
            if (!moveTimer->isActive()) {
                moveTimer->start(1000/FPS);
            }
        }
    }
    
    void PlayerCharacter::keyReleaseEvent(QKeyEvent *event)
    {
        // stop player moving left or right
        if (event->key() == Qt::Key_A || event->key() == Qt::Key_D) {
    
            if (moveTimer->isActive()) {
                moveTimer->stop();
            }
        }
    }
    
    void Character::move()
    {
        const double DISTANCE_MOVED_PER_FRAME = 1.0;
    
        if (direction == directions::right) {
            // player wants to move right
            setPos(x() + DISTANCE_MOVED_PER_FRAME,y());
        }
        else {
            // player wants to move left
            setPos(x() - DISTANCE_MOVED_PER_FRAME,y());
        }
    }
    

    I'm not sure if I'm understanding the timeout with the FPS and the DISTANCE_MOVED_PER_FRAME correctly as I'm still having trouble moving the sprite with speed in a smooth fashion.


  • Moderators

    You're starting the timer in a key press event so that skips a bit. That's one potential problem.
    The other is the timer - it runs at 1000/FPS interval. Is that synchronized to the monitor refresh somehow? Timers are not very good method for smooth animation. See my older response in another thread for some details about smooth animation.



  • Ok.

    So on the KeyPressEvent I mark the character as moving.

    On KeyReleaseEvent I mark moving as false for that character.

    During the game I check for the vertical sync and redraw during that event with the marked character at its new position.

    Is that right?

    I've had a look for a VSync event and I'm having trouble finding something in Qt directly. It seems that I need to use some OpenGL methods.

    Maybe something like OpenGL Window Example.

    My Game is placed in a QGraphicsView which contains the QGraphicsScene. The sprite is a QGraphicsItem. Would I be redrawing the entire View, Scene or just the Item? Or if I use the method from the OpenGL example do I need to do all of the drawing using OpenGL directly?



  • Did you ever solve this? Or is the conclusion that qGraphicsView can not be used to get smooth character movement?

    I've spent weeks googeling and experimenting but I am still unable to get movement that is not horribly choppy/stuttering or flawed in some other way. One of many examples:

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include <QGraphicsPixmapItem>
    #include <QKeyEvent>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        installEventFilter(this);
    
     /*
        //These settings result in smooth movement, but now the movement is affected by the window size
        //A small window will move much much faster than a big window
        moveSpeed = 1;
        frameRate = 0.5;
    */
    
        //This give same movement speed regardless of window size, but is instead very choppy
        moveSpeed = 8;
        frameRate = 1000/60;
    
        keyWActive =  keyAActive = keySActive = keyDActive = false;
    
        timerMove = new QTimer(this);
        timerMove->setTimerType(Qt::PreciseTimer);
        connect(timerMove, SIGNAL(timeout()), this, SLOT(move()));
    
    
        hScrollBar = ui->graphicsView->horizontalScrollBar();
        vScrollBar = ui->graphicsView->verticalScrollBar();
    
    
        graphicsScene = new QGraphicsScene(this);
        ui->graphicsView->setScene(graphicsScene);
    
        QPixmap pixmap(":/new/prefix1/trees_forest_4000x2656.jpg");
        QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
        graphicsScene->addItem(item);
    
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::move(){
        if (keyAActive){
           //using the ScrollBars is choppy, tried .translate() instead, same result
            //int x = hScrollBar->value();
            //hScrollBar->setValue(x-moveSpeed);
            ui->graphicsView->translate(moveSpeed,0);
        }
    
        if (keyWActive){
            //int y = vScrollBar->value();
            //vScrollBar->setValue(y-moveSpeed);
            ui->graphicsView->translate(0,moveSpeed);
        }
    
        if (keySActive){
            //int y = vScrollBar->value();
            //vScrollBar->setValue(y+moveSpeed);
            ui->graphicsView->translate(0,-moveSpeed);
        }
    
        if (keyDActive){
            //int x = hScrollBar->value();
            //hScrollBar->setValue(x+moveSpeed);
            ui->graphicsView->translate(-moveSpeed,0);
        }
    }
    
    void MainWindow::stopMove(){
        if (keyAActive==false && keyWActive==false && keySActive==false && keyDActive==false){
            timerMove->stop();
        }
    }
    
    bool MainWindow::eventFilter(QObject *obj, QEvent *event)
    {
        // This function repeatedly call for those QObjects
        // which have installed eventFilter (Step 2)
        if (obj == (QObject*)this){
    
    
            if (event->type() == QEvent::KeyPress){
                    QKeyEvent *KeyEvent = (QKeyEvent*)event;
    
                    switch(KeyEvent->key())
                    {
                    case Qt::Key_A:
                        if (keyAActive == false){
                            timerMove->start(frameRate);
                            keyAActive=true;
                        }
                        return true;
                    case Qt::Key_W:
                        if (keyWActive == false){
                            timerMove->start(frameRate);
                            keyWActive=true;
                        }
                        return true;
                    case Qt::Key_S:
                        if (keySActive == false){
                            timerMove->start(frameRate);
                            keySActive=true;
                        }
                        return true;
                    case Qt::Key_D:
                        if (keyDActive == false){
                            timerMove->start(frameRate);
                            keyDActive=true;                        
                        }
                        return true;
                    default:
                        break;
                    }
            }else if (event->type() == QEvent::KeyRelease){
                QKeyEvent *KeyEvent = (QKeyEvent*)event;
    
                switch(KeyEvent->key())
                {
                    case Qt::Key_W:
                       if (KeyEvent->isAutoRepeat()==false){
                           stopMove();
                           keyWActive=false;
                       }
                       return true;
                    case Qt::Key_A:
                        if (KeyEvent->isAutoRepeat()==false){
                            stopMove();
                            keyAActive=false;
                        }
                       return true;
                    case Qt::Key_S:
                        if (KeyEvent->isAutoRepeat()==false){
                            stopMove();
                            keySActive=false;
                        }
                       return true;
                    case Qt::Key_D:
                        if (KeyEvent->isAutoRepeat()==false){
                            stopMove();
                            keyDActive=false;                      
                        }
                       return true;
                    default:
                        break;
                }
    
            }    return QWidget::eventFilter(obj, event);
           }else {
                // pass the event on to the parent class
                return QWidget::eventFilter(obj, event);
            }
    }
    


  • Hi,

    I don't know if there's a solution for this yet, but for creating games with Qt, you can also have a look at V-Play Engine for qt-based mobile games and apps.

    The QML-based gaming components make it easy to add animations, movement, physics or collision detection.

    In addition, the SDK comes with many full-featured open-source demos and tutorials, for example to create a Flappy Bird game.

    Best,
    GT


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.