Evgenii Legotckoi
Evgenii LegotckoiSept. 19, 2015, 11:33 a.m.

How to make game using Qt - Lesson 2. Animation game hero (2D)

In the second lesson we'll get to the next aspect of the writing of the game - the hero Animation. In the last lesson it was considered as a control object in the graphic scene, and the main character played a red triangle, but it is not very interesting. So we will turn the triangle into an animate object, namely the Fly, which will crawl under the control of the keyboard and move the legs when moving.

In this lesson, the adjustment made in the code triangle.h, triangle.cpp and just a little in widget.cpp file.

Animation Fly on steps

We describe an algorithm, which should be the rendering Flies:

  1. Initialize variable in the class constructor, which will be responsible for the number of legs position. There will be three positions fly legs.
  2. Initialize variable in the class constructor, which will be useful to consider the timer ticks of the game, then those ticks during which the fly moved. It is necessary to weed out those tics, in which there was no movement Flies Fly otherwise be constantly iterate legs, even if we do not manage Fly .
  3. Draw Fly in the paint method, choosing which of the provisions of the flies legs will be drawn.
  4. The slot that handles the event starting the game timer produces accumulation and reset the counter ticks useful game timer and depending on its value set position Flies legs, namely completely redraw the Fly with the correct position of the legs.

triangle.h

In this file add only two integer variables:

  • steps - position number of flies legs;
  • countForSteps - a counter for counting clock ticks for which the keyboard interface produced.
#ifndef TRIANGLE_H
#define TRIANGLE_H

#include <QObject>
#include <QGraphicsItem>
#include <QPainter>
#include <QGraphicsScene>

#include <windows.h>

class Triangle : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    explicit Triangle(QObject *parent = 0);
    ~Triangle();

signals:

public slots:
    void slotGameTimer(); 

protected:
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

private:
    qreal angle;     
    int steps;          // Number position flies legs
    int countForSteps;  // The counter for counting clock ticks for which we pressed the button

};

#endif // TRIANGLE_H

triangle.cpp

In general, changes were made completely whole file. The class constructor assigns values to two new variables. In the paint method is completely rendered the whole fly with three possible positions of the feet. To apply draw objects such as ellipses, lines and QPainterPath class objects.

In order to fly turned to the appropriate appearance, it was made a sketch of Flies, which coordinates the program was moved to the lesson code.

With the fun is present in the method slotGameTimer() , in which the tracking button states conducted countdown useful ticks, and if countForSteps variable equal to 4, 8, 12 and 16, chosen one of the provisions of the Flies legs and initiated redrawing Flies with this provision, legs.

#include "triangle.h"

Triangle::Triangle(QObject *parent) :
    QObject(parent), QGraphicsItem()
{
    angle = 0;      
    steps = 1;      // Set starting position flies legs
    countForSteps = 0;      // The counter for counting clock ticks for which we pressed the button
    setRotation(angle);     
}

Triangle::~Triangle()
{

}

QRectF Triangle::boundingRect() const
{
    return QRectF(-40,-50,80,100);  
}

void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    // Draw the legs, without legs as a fly can not creep
    painter->setPen(QPen(Qt::black, 2));
    if(steps == 0){     // The first position of the legs
        // Left 1
        painter->drawLine(-24,-37,-22,-25);
        painter->drawLine(-22,-25,-17,-15);
        painter->drawLine(-17,-15,-10,-5);
        // Right 1
        painter->drawLine(37,-28,28,-18);
        painter->drawLine(28,-18,24,-8);
        painter->drawLine(24,-8,10,-5);

        // Left 2
        painter->drawLine(-35,-20,-25,-11);
        painter->drawLine(-25,-11,-14,-5);
        painter->drawLine(-14,-5,0,5);
        // Right 2
        painter->drawLine(37,-12,32,-4);
        painter->drawLine(32,-4,24,2);
        painter->drawLine(24,2,0,5);

        // Left 3
        painter->drawLine(-35,35,-26,24);
        painter->drawLine(-26,24,-16,16);
        painter->drawLine(-16,16,0,0);
        // Right 3
        painter->drawLine(37,26,32,17);
        painter->drawLine(32,17,24,8);
        painter->drawLine(24,8,0,0);
    } else if (steps == 1){     // The second position of the legs
        // Left 1
        painter->drawLine(-32,-32,-25,-22);
        painter->drawLine(-25,-22,-20,-12);
        painter->drawLine(-20,-12,-10,-5);
        // Right 1
        painter->drawLine(32,-32,25,-22);
        painter->drawLine(25,-22,20,-12);
        painter->drawLine(20,-12,10,-5);

        // Left 2
        painter->drawLine(-39,-15,-30,-8);
        painter->drawLine(-30,-8,-18,-2);
        painter->drawLine(-18,-2,0,5);
        // Right 2
        painter->drawLine(39,-15,30,-8);
        painter->drawLine(30,-8,18,-2);
        painter->drawLine(18,-2,0,5);

        // Left 3
        painter->drawLine(-39,30,-30,20);
        painter->drawLine(-30,20,-20,12);
        painter->drawLine(-20,12,0,0);
        // Right 3
        painter->drawLine(39,30,30,20);
        painter->drawLine(30,20,20,12);
        painter->drawLine(20,12,0,0);
    } else if (steps == 2){     // The third position of the legs
        // Left 1
        painter->drawLine(-37,-28,-28,-18);
        painter->drawLine(-28,-18,-24,-8);
        painter->drawLine(-24,-8,-10,-5);
        // Right 1
        painter->drawLine(24,-37,22,-25);
        painter->drawLine(22,-25,17,-15);
        painter->drawLine(17,-15,10,-5);

        // Left 2
        painter->drawLine(-37,-12,-32,-4);
        painter->drawLine(-32,-4,-24,2);
        painter->drawLine(-24,2,0,5);
        // Right 2
        painter->drawLine(35,-20,25,-11);
        painter->drawLine(25,-11,14,-5);
        painter->drawLine(14,-5,0,5);

        // Left 3
        painter->drawLine(-37,26,-32,17);
        painter->drawLine(-32,17,-24,8);
        painter->drawLine(-24,8,0,0);
        // Right 3
        painter->drawLine(35,35,26,24);
        painter->drawLine(26,24,16,16);
        painter->drawLine(16,16,0,0);
    }
    // Antennae
    QPainterPath path(QPointF(-5,-34));
    path.cubicTo(-5,-34, 0,-36,0,-30);
    path.cubicTo(0,-30, 0,-36,5,-34);
    painter->setBrush(Qt::NoBrush);
    painter->drawPath(path);

    painter->setPen(QPen(Qt::black, 1));
    // carcass
    painter->setBrush(Qt::black);
    painter->drawEllipse(-15, -20, 30, 50);
    // Head
    painter->drawEllipse(-15, -30, 30, 20);
    // eyes
    painter->setBrush(Qt::green);
    painter->drawEllipse(-15, -27, 12, 15);
    painter->drawEllipse(3, -27, 12, 15);

    // Left wing
    QPainterPath path2(QPointF(-10, -10));
    path2.cubicTo(-18, -10, -30, 10, -25, 35);
    path2.cubicTo(-25,35,-20,50,-15,40);
    path2.cubicTo(-15,40,0,20,-3,5 );
    path2.cubicTo(-3,5, -8,8,-10,-10);
    painter->setBrush(Qt::white);
    painter->drawPath(path2);

    // Right wing
    QPainterPath path3(QPointF(10, -10));
    path3.cubicTo(18, -10, 30, 10, 25, 35);
    path3.cubicTo(25,35,20,50,15,40);
    path3.cubicTo(15,40,0,20,3,5 );
    path3.cubicTo(3,5, 8,8,10,-10);
    painter->setBrush(Qt::white);
    painter->drawPath(path3);

    Q_UNUSED(option);
    Q_UNUSED(widget);
}

void Triangle::slotGameTimer()
{
    /* Check if pressed any of the object control buttons. Before you consider steps
     * */
    if(GetAsyncKeyState(VK_LEFT) ||
       GetAsyncKeyState(VK_RIGHT) ||
       GetAsyncKeyState(VK_UP) ||
       GetAsyncKeyState(VK_DOWN))
    {
        if(GetAsyncKeyState(VK_LEFT)){
            angle -= 5;        
            setRotation(angle); 
        }
        if(GetAsyncKeyState(VK_RIGHT)){
            angle += 5;        
            setRotation(angle); 
        }
        if(GetAsyncKeyState(VK_UP)){
            setPos(mapToParent(0, -2));    
        }
        if(GetAsyncKeyState(VK_DOWN)){
            setPos(mapToParent(0, 2));      
        }

        countForSteps++;
        if(countForSteps == 4){
            steps = 2;
            update(QRectF(-40,-50,80,100));
        } else if (countForSteps == 8){
            steps = 1;
            update(QRectF(-40,-50,80,100));
        } else if (countForSteps == 12){
            steps = 0;
            update(QRectF(-40,-50,80,100));
        } else if (countForSteps == 16) {
            steps = 1;
            update(QRectF(-40,-50,80,100));
            countForSteps = 0;
        }
    }

    if(this->x() - 10 < -250){
        this->setX(-240);      
    }
    if(this->x() + 10 > 250){
        this->setX(240);       
    }

    if(this->y() - 10 < -250){
        this->setY(-240);       
    }
    if(this->y() + 10 > 250){
        this->setY(240);        
    }
}

widget.cpp

To improve the smoothness of drawing was increased frequency of signal processing from the timer.

timer->start(1000 / 100);

Result

As a result, you should get a fly that the motion will cycle through the tabs.

A full list of articles in this series:

Video

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

M
  • April 25, 2018, 5:16 a.m.

Здравствуйте,
Подскажите, почему муха может оставлять следы на игровой сцене?

Evgenii Legotckoi
  • April 25, 2018, 5:51 a.m.

Добрый день. Артефакты обычно остаются в том случае, если не обновляется полностью тот участок графической сцены, в котором находится муха. Скорее возможно boundingRect() написали не совсем правильно, если писали его самостоятельно для своего собственного варианта. Также как вариант можете запускать метод update() на графической сцене с указание того пространства, которое требуется перерисовать.

Илья Некрасов
  • March 27, 2020, 2:28 p.m.
  • (edited)

Здравствуйте, а можно, пожалуйста, ссылку на целые исходники, если есть?

Evgenii Legotckoi
  • March 27, 2020, 2:40 p.m.

Добрый день. В конце пятой статьи скачать можете.

J
  • May 20, 2023, 11:27 a.m.

Евгений, здравствуйте! Подскажите, а почему при нажатии одной клавиши переменная countForSteps увеличивается не на 1, на 4, ведь одно действие даёт увеличение этой переменной только на единицу? (стр.184 в файле triangle.cpp)

Evgenii Legotckoi
  • May 21, 2023, 5:57 a.m.

Добрый день. slotGameTimer срабатывает по таймеру и при каждой сработке countForSteps увеличивается на 1, это не зависит от нажатия клавиш, нажатая клавиша лишь определяет положение ножек, которое отрисовывается у игрового персонажа. В коде есть часть, которая отвечает за отрисовку и эта часть делает проверку на число, кратное 4, поэтому вам и показалось, что countForSteps увеличивается на 4, а на самом деле во время нажатя клавиши слот успевает вызываться 4 раза.

J
  • May 21, 2023, 10:49 a.m.
  • (edited)

Евгений, благодарю! Всё равно не совсем понимаю :( Если муха двигает ножками только при нажатии клавиш перемещение, то что, собственно, делает код со строк 184-198 в triangle.cpp? В этих строчках код опеределяет "характер" движения ножек в зависимости от направления движения мухи. Но я не очень понимаю как эта часть кода связана с разными нажатиями кнопок игроком, которые, собственно, и задают "характер" движения ножек. Ведь, как я понял, таймер работает не только во время нажатия клавиш, так? Плюс, не понимаю, почему слот за одно движение срабатывает 4 раза... Прошу прощение, немного запутался :(

Evgenii Legotckoi
  • May 25, 2023, 4:49 a.m.

Код на строчка 184-198 вызывает перерисовку области на каждый 4-й такт счётчика. По той логике не нужно перерисовывать объект постоянно, достаточно реже, чем выполняется игровой слот. А слот выполняется постоянно по таймеру. Потому, что как вы правильно поняли, таймер работает и тогда, когда клавиши не нажимаются. А сработка 4 раза или даже чаще - это потому что нажатие обычно дольше, чем длительность отсчёта таймера. Слот срабатывает по таймеру, таймер срабатывает постоянно. Почему именно 4 раза? - Потому, что это эмпирически подобранная величина.

J
  • May 25, 2023, 2:24 p.m.

Евгений, благодарю!

J
  • Aug. 3, 2023, 3:56 a.m.

Евгейни, скажите: а почему вы не использовали в этом проекте класс событий нажатия клавиш QKeyPressEvent? Это менее целесообразно в данном случае было?

Evgenii Legotckoi
  • Aug. 3, 2023, 4:43 a.m.

Да, менее целесообразно. Не подходит, поскольку не даёт обрабатывать постоянное нажатие кнопки достаточно простым способом.
Во всяком случае это первая причина, которая мне вспоминается. Я уже почти ничего не помню с момента написания этого поста, касательно этого примера.

J
  • Aug. 3, 2023, 5:19 a.m.
  • (edited)

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

Evgenii Legotckoi
  • Aug. 8, 2023, 2:13 a.m.

События нажатий кнопок в Qt одноразовые. То есть пришло событие и вам нужно куда-то сохранить информацию об этом.
А лично на мой взгляд гораздо проще проверять нажата ли кнопка через Win Api в тот момент времени, когда это понадобится.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
L
  • Leo
  • Sept. 26, 2023, 11:43 a.m.

C++ - Test 002. Constants

  • Result:41points,
  • Rating points-8
L
  • Leo
  • Sept. 26, 2023, 11:32 a.m.

C++ - Test 001. The first program and data types

  • Result:93points,
  • Rating points8
Last comments
IscanderChe
IscanderCheSept. 13, 2023, 9:11 a.m.
QScintilla C++ example По горячим следам (с другого форума вопрос задали, пришлось в памяти освежить всё) решил дополнить. Качаем исходники с https://riverbankcomputing.com/software/qscintilla/downlo…
Evgenii Legotckoi
Evgenii LegotckoiSept. 6, 2023, 7:18 a.m.
Qt/C++ - Lesson 048. QThread — How to work with threads using moveToThread Разве могут взаимодействовать объекты из разных нитей как-то, кроме как через сигнал-слоты?" Могут. Выполняя оператор new , Вы выделяете под объект память в куче (heap), …
AC
Andrei CherniaevSept. 5, 2023, 3:37 a.m.
Qt/C++ - Lesson 048. QThread — How to work with threads using moveToThread Я поясню свой вопрос. Выше я писал "Почему же в методе MainWindow::on_write_1_clicked() Можно обращаться к методам exampleObject_1? Разве могут взаимодействовать объекты из разных…
n
nvnAug. 31, 2023, 9:47 a.m.
QML - Lesson 004. Signals and Slots in Qt QML Здравствуйте! Прекрасный сайт, отличные статьи. Не хватает только готовых проектов для скачивания. Многих комментариев типа appCore != AppCore просто бы не было )))
NSProject
NSProjectAug. 24, 2023, 1:40 p.m.
Django - Tutorial 023. Like Dislike system using GenericForeignKey Ваша ошибка связана с gettext from django.utils.translation import gettext_lazy as _ Поле должно выглядеть так vote = models.SmallIntegerField(verbose_name=_("Голос"), choices=VOTES) …
Now discuss on the forum
IscanderChe
IscanderCheSept. 17, 2023, 9:24 a.m.
Интернационализация строк в QMessageBox Странная картина... Сделал минимально работающий пример - всё работает. Попробую на другой операционке. Может, дело в этом.
NSProject
NSProjectSept. 17, 2023, 8:49 a.m.
Помогите добавить Ajax в проект В принципе ничего сложного с отправкой на сервер нет. Всё что ты хочешь отобразить на странице передаётся в шаблон и рендерится. Ты просто создаёшь файл forms.py в нём описываешь свою форму и в …
BlinCT
BlinCTSept. 15, 2023, 12:35 p.m.
Размеры полей в TreeView Всем привет. Пытаюсь сделать дерево вот такого вида Пытаюсь организовать делегат для каждой строки в дереве. ТО есть отступ какого то размера и если при открытии есть под…
IscanderChe
IscanderCheSept. 8, 2023, 12:07 p.m.
Кастомная QAbstractListModel и цвет фона, цвет текста и шрифт Похоже надо не абстрактный , а "реальный" типа QSqlTableModel Да, но не совсем. Решилось с помощью стайлшитов и setFont. Спасибо за отлик!
Evgenii Legotckoi
Evgenii LegotckoiSept. 6, 2023, 6:35 a.m.
Вопрос: Нужно ли в деструкторе удалять динамически созданные QT-объекты. Напр: Зависит от того, как эти объекты были созданы. Если вы передаёте указатель на parent объект, то не нужно, Ядро Qt само разрулит удаление, если нет, то нужно удалять вручную, иначе будет ут…

Follow us in social networks