- 1. Animation Fly on steps
- 2. triangle.h
- 3. triangle.cpp
- 4. widget.cpp
- 5. Result
- 6. Video
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:
- Initialize variable in the class constructor, which will be responsible for the number of legs position. There will be three positions fly legs.
- 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 .
- Draw Fly in the paint method, choosing which of the provisions of the flies legs will be drawn.
- 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:
- Lesson 1. How to make game using Qt. Control of object
- Lesson 2. How to make game using Qt. Animation game hero (2D)
- Lesson 3. How to make game using Qt. Interaction with other objects
- Lesson 4. How to make game using Qt. Enemy – meaning in the survival
- Lesson 5. How to make game using Qt. Adding sound QMediaPlayer
Здравствуйте,
Подскажите, почему муха может оставлять следы на игровой сцене?
Добрый день. Артефакты обычно остаются в том случае, если не обновляется полностью тот участок графической сцены, в котором находится муха. Скорее возможно boundingRect() написали не совсем правильно, если писали его самостоятельно для своего собственного варианта. Также как вариант можете запускать метод update() на графической сцене с указание того пространства, которое требуется перерисовать.
Здравствуйте, а можно, пожалуйста, ссылку на целые исходники, если есть?
Добрый день. В конце пятой статьи скачать можете.
Евгений, здравствуйте! Подскажите, а почему при нажатии одной клавиши переменная countForSteps увеличивается не на 1, на 4, ведь одно действие даёт увеличение этой переменной только на единицу? (стр.184 в файле triangle.cpp)
Добрый день. slotGameTimer срабатывает по таймеру и при каждой сработке countForSteps увеличивается на 1, это не зависит от нажатия клавиш, нажатая клавиша лишь определяет положение ножек, которое отрисовывается у игрового персонажа. В коде есть часть, которая отвечает за отрисовку и эта часть делает проверку на число, кратное 4, поэтому вам и показалось, что countForSteps увеличивается на 4, а на самом деле во время нажатя клавиши слот успевает вызываться 4 раза.
Евгений, благодарю! Всё равно не совсем понимаю :( Если муха двигает ножками только при нажатии клавиш перемещение, то что, собственно, делает код со строк 184-198 в triangle.cpp? В этих строчках код опеределяет "характер" движения ножек в зависимости от направления движения мухи. Но я не очень понимаю как эта часть кода связана с разными нажатиями кнопок игроком, которые, собственно, и задают "характер" движения ножек. Ведь, как я понял, таймер работает не только во время нажатия клавиш, так? Плюс, не понимаю, почему слот за одно движение срабатывает 4 раза... Прошу прощение, немного запутался :(
Код на строчка 184-198 вызывает перерисовку области на каждый 4-й такт счётчика. По той логике не нужно перерисовывать объект постоянно, достаточно реже, чем выполняется игровой слот. А слот выполняется постоянно по таймеру. Потому, что как вы правильно поняли, таймер работает и тогда, когда клавиши не нажимаются. А сработка 4 раза или даже чаще - это потому что нажатие обычно дольше, чем длительность отсчёта таймера. Слот срабатывает по таймеру, таймер срабатывает постоянно. Почему именно 4 раза? - Потому, что это эмпирически подобранная величина.
Евгений, благодарю!
Евгейни, скажите: а почему вы не использовали в этом проекте класс событий нажатия клавиш QKeyPressEvent? Это менее целесообразно в данном случае было?
Да, менее целесообразно. Не подходит, поскольку не даёт обрабатывать постоянное нажатие кнопки достаточно простым способом.
Во всяком случае это первая причина, которая мне вспоминается. Я уже почти ничего не помню с момента написания этого поста, касательно этого примера.
Евгений, я новичок в Qt и решил попрактиковать в создании какой-нибудь простенькой игры (змейка, тетрис, морской бой), где присутствует постоянное нажатие клавиш. На ваш опытный взгляд, можно ли сказать, что в таких и похожих ситуациях лучше не влезать в класс обработки событий клавиатуры из-за его насыщенности, а просто прибегнуть к подключению возможностей Win32 как вы сделали в этой игре?
События нажатий кнопок в Qt одноразовые. То есть пришло событие и вам нужно куда-то сохранить информацию об этом.
А лично на мой взгляд гораздо проще проверять нажата ли кнопка через Win Api в тот момент времени, когда это понадобится.