Екінші сабақта ойын жазудың келесі аспектісіне көшейік - Ойын кейіпкерінің анимациясы. Басты кейіпкер қызыл үшбұрыш болды, бірақ бұл өте қызықты емес. Сондықтан үшбұрышты анимациялық нысанға, атап айтқанда пернетақтаның басқаруымен жорғалап, қозғалған кезде аяқтарын қозғалтатын Ұшуға айналдырайық.
Бұл сабақта бағдарлама коды негізінен triangle.h, triangle.cpp файлдарында және widget.cpp. файлында өте аз түзетілді.
Анимацияны кезең-кезеңімен орындаңыз
Fly сызылатын алгоритмді сипаттайық:
- Біз класс конструкторында аяқтардың позиция нөміріне жауап беретін айнымалыны инициализациялаймыз. Барлығы шыбынның аяқтарының үш позициясы болады.
- Класс конструкторында ойын таймерінің пайдалы белгілерін, содан кейін Fly жылжытылған белгілерді санайтын айнымалы мәнді инициализациялаймыз. Бұл Шыбын қозғалысы болмаған кенелерді жою үшін қажет, әйтпесе Шыбын басқармасақ та, Шыбын үнемі аяқтарын жылжытады.
- Шыбынның аяқтарының қайсысы сызылатынын таңдап, бояу, әдісімен Шыбын суретін сал.
- Ойын таймерінің кері санақ оқиғасын өңдейтін ұяшықта ол ойын таймерінің пайдалы кенелерінің есептегішін жинақтайды және қалпына келтіреді және оның мәніне байланысты Ұшудың аяқтарының орнын орнатады, атап айтқанда , аяқтың қалаған күйімен Fly -ді толығымен қайта сызады.
үшбұрыш.сағ
Бұл файлға тек екі бүтін айнымалыны қосамыз:
- қадамдар - шыбынның аяқтарының позициясының нөмірі;
- countForSteps - пернетақта пернелері басылған таймер белгілерін санауға арналған есептегіш.
#ifndef TRIANGLE\_H #define TRIANGLE\_H #include <QObject> #include <QGraphicsItem> #include <QPainter> #include <QGraphicsScene> /* Подключаем библиотеку, отвечающую за использование WinAPI * Данная библиотека необходима для асинхронной проверки состояния клавиш * */ #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; // Номер положения ножек мухи int countForSteps; // Счётчик для отсчета тиков таймера, при которых мы нажимали на кнопки }; #endif // TRIANGLE\_H
triangle.cpp
Жалпы, өзгерістер бүкіл файлға әсер етті. Класс конструкторы екі жаңа айнымалыға мәндер тағайындайды. Ал бояу әдісінде бүкіл Шыбын аяқтың үш ықтимал позициясымен толығымен сызылады. Сурет салу үшін эллипс, сызықтар және QPainterPath. класының объектілері сияқты нысандар пайдаланылады.
Шыбынның сәйкес келбеті бар болуы үшін координаталар бойынша сабақтың бағдарламалық кодына көшірілген Шыбынның эскизі жасалды.
Ең қызығы slotGameTimer() әдісінде бар, бұл әдісте түймелердің күйін бақылау кезінде пайдалы белгілер есептеледі және countForSteps айнымалысы 4, 8, 12-ге тең болса. және 16, аяқтардың позицияларының бірі таңдалады Шыбындар және аяқтардың осы қалпымен Шыбындардың қайта сызуын бастайды.
#include "triangle.h" Triangle::Triangle(QObject *parent) : QObject(parent), QGraphicsItem() { angle = 0; // Задаём угол поворота графического объекта steps = 1; // Задаём стартовое положение ножек мухи countForSteps = 0; // Счётчик для отсчета тиков таймера, при которых мы нажимали на кнопки setRotation(angle); // Устанавливаем угол поворота графического объекта } Triangle::~Triangle() { } QRectF Triangle::boundingRect() const { return QRectF(-40,-50,80,100); /// Ограничиваем область, в которой лежит треугольник } void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // Рисуем ножки, без ножек же муха не сможет ползать painter->setPen(QPen(Qt::black, 2)); if(steps == 0){ // Первое положение ножек // 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){ // Второе положение ножек // 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){ // Третье положение ножек // 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); } // Усики 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)); // Тушка painter->setBrush(Qt::black); painter->drawEllipse(-15, -20, 30, 50); // Голова painter->drawEllipse(-15, -30, 30, 20); // Глазища painter->setBrush(Qt::green); painter->drawEllipse(-15, -27, 12, 15); painter->drawEllipse(3, -27, 12, 15); // Левое крылище 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); // Правое крылище 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() { /* Проверяем, нажата ли была какая-либо из кнопок управления объектом. * Прежде чем считать шажки * */ if(GetAsyncKeyState(VK\_LEFT) || GetAsyncKeyState(VK\_RIGHT) || GetAsyncKeyState(VK\_UP) || GetAsyncKeyState(VK\_DOWN)) { /* Поочерёдно выполняем проверку на нажатие клавиш * с помощью функции асинхронного получения состояния клавиш, * которая предоставляется WinAPI * */ if(GetAsyncKeyState(VK\_LEFT)){ angle -= 5; // Задаём поворот на 5 градусов влево setRotation(angle); // Поворачиваем объект } if(GetAsyncKeyState(VK\_RIGHT)){ angle += 5; // Задаём поворот на 5 градусов вправо setRotation(angle); // Поворачиваем объект } if(GetAsyncKeyState(VK\_UP)){ setPos(mapToParent(0, -2)); /* Продвигаем объект на 5 пискселей вперёд * перетранслировав их в координатную систему * графической сцены * */ } if(GetAsyncKeyState(VK\_DOWN)){ setPos(mapToParent(0, 2)); /* Продвигаем объект на 5 пискселей назад * перетранслировав их в координатную систему * графической сцены * */ } // Двигаем ножками, Dance, dance, Baby !!! 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
Көрсетудің бірқалыптылығын жақсарту үшін таймерден сигнал жиілігі ұлғайтылды.
timer->start(1000 / 100);
Барлығы
Нәтижесінде сіз қозғалған кезде табандарына тиетін Шыбын алуыңыз керек. Сондай-ақ осы мақаладағы бейне оқулықта осы сабақтың нәтижесінің демонстрациясын көре аласыз.
Осы сериядағы мақалалардың толық тізімі:
- 1-сабақ. Qt тілінде ойынды қалай жазуға болады. Объектіні басқару
- 2-сабақ. Qt ойынды қалай жазуға болады. Ойын кейіпкерінің анимациясы (2D)
- 3-сабақ. Qt ойынды қалай жазуға болады. Басқа нысандармен әрекеттесу
- 4-сабақ. Qt ойынды қалай жазуға болады. Жау – аман қалудың мәні
- 5-сабақ. Qt ойынды қалай жазуға болады. 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 в тот момент времени, когда это понадобится.