- 1. Ворог мухи у структурі проекту
- 2. spider.h
- 3. spider.cpp
- 4. віджет.h
- 5. widget.cpp
- 6. Підсумок
- 7. Відеоурок
Продовжуємо тему, як написати гру на Qt. Після того, як у минулих статтях було створено Муху, яка поїдає яблука, настав час створити їй ворога. А Ворог Мухи , як відомо, Павук . Створення ігрових персонажів, які братимуть участь у безпосередньому житті Вашого головного героя - це не лише малювання анімації дій та пересування, а також логіка реакцій на впливи гравця, а й штучний інтелект, відповідно до логіки якого визначатиметься поведінка ігрового персонажа. Таким чином, ми додаємо в гру новий сенс, не тільки з'їсти якнайбільше яблук, але й вижити за всяку ціну.
Визначимо поведінку Павука у даній версії гри. Що ж він має робити? Та найпростіше з усіх дій - полювати на Муху, просто ганятися за нею по ігровому полю.
Також додамо в гру кнопку для запуску ігрового процесу, і паузу, і найголовніше, що додамо - це Game Over.
Ворог мухи у структурі проекту
Як і у випадку з Мухою до структури проекту додається додатковий клас, який відповідатиме за об'єкт, яким є Павук .
- spider.h - заголовний файл класу, що відповідає за Павука
- spider.cpp - файл вихідних кодів, що відповідає за Павука
spider.h
Відмінність даного файла від заголовного файлу мухи полягає в тому, що в ньому оголошено ігровий таймер, який відповідає за поведінку Павука, тобто ворог Мухи тактується не від таймера в ядрі гри, а від власного внутрішнього таймера. Також при ініціалізації павука, в нього закладається його мета, тобто Муха , за якою він невпинно слідує. В даному випадку штучний інтелект примітивний до неподобства, але більшого на даний момент не вимагається.
#ifndef SPIDER_H #define SPIDER_H #include <QObject> #include <QGraphicsItem> #include <QGraphicsScene> #include <QPainter> #include <QTimer> #include <QDebug> class Spider : public QObject, public QGraphicsItem { Q_OBJECT public: explicit Spider(QGraphicsItem * target, QObject *parent = 0); ~Spider(); void pause(); // Сигнал для инициализации паузы signals: void signalCheckGameOver(); // Сигнал на вызов состояния Game Over public slots: protected: QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); private: qreal angle; // Угол поворота графического объекта int steps; // Номер положения ножек паука int countForSteps; // Счетчик для изменения полоэения ножек QTimer *timer; // Внутренний таймер паука, по которому инициализируется его движение QGraphicsItem * target; // Цель паука, данный объект приравнивается объекту Мухи private slots: void slotGameTimer(); // Слот игрового таймера паука }; #endif // SPIDER_H
spider.cpp
Вихідний код Павука аналогічний вихідному коду Мухи , з тією відмінністю, що в ігровому Слоті Павука відстежується положення Мухи на ігровому полі, і Павук розгортається убік * Мухи і повзе за нею. А як тільки ворог мухи натикається на якийсь із об'єктів на графічній сцені, то він проводить перевірку, чи є цей об'єкт Мухою , якщо так, то ініціалізується процедура Game Over * .
#include "spider.h" #include "math.h" static const double Pi = 3.14159265358979323846264338327950288419717; static double TwoPi = 2.0 * Pi; static qreal normalizeAngle(qreal angle) { while (angle < 0) angle += TwoPi; while (angle > TwoPi) angle -= TwoPi; return angle; } Spider::Spider(QGraphicsItem *target, QObject *parent) : QObject(parent), QGraphicsItem() { angle = 0; // Задаём угол поворота графического объекта steps = 0; // Задаём стартовое положение ножек мухи countForSteps = 0; // Счётчик для отсчета тиков таймера, при которых мы нажимали на кнопки setRotation(angle); // Устанавливаем угол поворота графического объекта this->target = target; // Устанавливаем цель паука timer = new QTimer(); // Инициализируем игровой таймер паука // подключаем сигнал таймера к игровому слоту паука connect(timer, &QTimer::timeout, this, &Spider::slotGameTimer); timer->start(15); // Запускаем таймер } Spider::~Spider() { } QRectF Spider::boundingRect() const { return QRectF(-40,-50,80,100); } void Spider::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // Рисуем ножки, без ножек паук не догонит муху painter->setPen(QPen(Qt::black, 2)); if(steps == 0){ // Первое положение ножек // Left 1 painter->drawLine(-24,-45,-28,-35); painter->drawLine(-28,-35,-22,-10); painter->drawLine(-22,-10,0,0); // Right 1 painter->drawLine(24,-45,28,-35); painter->drawLine(28,-35,22,-10); painter->drawLine(22,-10,0,0); // Left 2 painter->drawLine(-35,-38,-30,-18); painter->drawLine(-30,-18,-25,-3); painter->drawLine(-25,-3,0,0); // Right 2 painter->drawLine(35,-38,30,-18); painter->drawLine(30,-18,25,-3); painter->drawLine(25,-3,0,0); // Left 3 painter->drawLine(-35,38,-30,18); painter->drawLine(-30,18,-25,3); painter->drawLine(-25,3,0,0); // Right 3 painter->drawLine(35,38,30,18); painter->drawLine(30,18,25,3); painter->drawLine(25,3,0,0); // Left 4 painter->drawLine(-24,45,-28,35); painter->drawLine(-28,35,-22,10); painter->drawLine(-22,10,0,0); // Right 4 painter->drawLine(24,45,28,35); painter->drawLine(28,35,22,10); painter->drawLine(22,10,0,0); } else if (steps == 1){ // Второе положение ножек // Left 1 painter->drawLine(-23,-40,-24,-30); painter->drawLine(-24,-30,-19,-9); painter->drawLine(-19,-9,0,0); // Right 1 painter->drawLine(20,-50,23,-40); painter->drawLine(23,-40,15,-12); painter->drawLine(15,-12,0,0); // Left 2 painter->drawLine(-30,-35,-27,-24); painter->drawLine(-27,-24,-23,-5); painter->drawLine(-23,-5,0,0); // Right 2 painter->drawLine(40,-27,35,-10); painter->drawLine(35,-10,28,-1); painter->drawLine(28,-1,0,0); // Left 3 painter->drawLine(-40,27,-35,10); painter->drawLine(-35,10,-28,1); painter->drawLine(-28,1,0,0); // Right 3 painter->drawLine(30,35,27,24); painter->drawLine(27,24,23,5); painter->drawLine(23,5,0,0); // Left 4 painter->drawLine(-20,50,-27,30); painter->drawLine(-27,30,-20,12); painter->drawLine(-20,12,0,0); // Right 4 painter->drawLine(23,40,24,30); painter->drawLine(24,30,19,9); painter->drawLine(19,9,0,0); } else if (steps == 2){ // Третье положение ножек // Left 1 painter->drawLine(-20,-50,-23,-40); painter->drawLine(-23,-40,-15,-12); painter->drawLine(-15,-12,0,0); // Right 1 painter->drawLine(23,-40,24,-30); painter->drawLine(24,-30,19,-9); painter->drawLine(19,-9,0,0); // Left 2 painter->drawLine(-40,-27,-35,-10); painter->drawLine(-35,-10,-28,-1); painter->drawLine(-28,-1,0,0); // Right 2 painter->drawLine(30,-35,27,-24); painter->drawLine(27,-24,23,-5); painter->drawLine(23,-5,0,0); // Left 3 painter->drawLine(-30,35,-27,24); painter->drawLine(-27,24,-23,5); painter->drawLine(-23,5,0,0); // Right 3 painter->drawLine(40,27,35,10); painter->drawLine(35,10,28,1); painter->drawLine(28,1,0,0); // Left 4 painter->drawLine(-23,40,-24,30); painter->drawLine(-24,30,-19,9); painter->drawLine(-19,9,0,0); // Right 4 painter->drawLine(20,50,27,30); painter->drawLine(27,30,20,12); painter->drawLine(20,12,0,0); } painter->setPen(QPen(Qt::black, 1)); // Левое Жвало QPainterPath path1(QPointF(0, -20)); path1.cubicTo(0, -20, -5, -25, -3, -35); path1.cubicTo(-3,-35,-15,-25,-8,-17); path1.cubicTo(-8,-17,-5,15,0,-20 ); painter->setBrush(Qt::black); painter->drawPath(path1); // Правое Жвало QPainterPath path2(QPointF(0, -20)); path2.cubicTo(0, -20, 5, -25, 3, -35); path2.cubicTo(3,-35,15,-25,8,-17); path2.cubicTo(8,-17,5,15,0,-20 ); painter->setBrush(Qt::black); painter->drawPath(path2); // Голова painter->setBrush(QColor(146, 115, 40, 255)); painter->drawEllipse(-10,-25,20,15); // Тушка painter->drawEllipse(-15, -15, 30, 30); // Жопка painter->drawEllipse(-20, 0, 40,50); painter->setPen(QPen(Qt::white,3)); painter->drawLine(-10,25,10,25); painter->drawLine(0,35,0,15); // Левое глазище painter->setPen(QPen(Qt::black,1)); painter->setBrush(Qt::red); painter->drawEllipse(-8,-23,6,8); // Правое глазище painter->setBrush(Qt::red); painter->drawEllipse(2,-23,6,8); Q_UNUSED(option); Q_UNUSED(widget); } void Spider::slotGameTimer() { // Определяем расстояние до Мухи QLineF lineToTarget(QPointF(0, 0), mapFromItem(target, 0, 0)); // Угол поворота в направлении к Мухе qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length()); if (lineToTarget.dy() < 0) angleToTarget = TwoPi - angleToTarget; angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2); /* В Зависимости от того, слева или справа находится Муха от Паука, * устанавливаем направление поворота паука в данном тике таймера * Скорость разворота зависит от угла, на который необходимо повернуться пауку * */ if (angleToTarget > 0 && angleToTarget < Pi) { // Rotate left if(angleToTarget > Pi / 5){ angle = -15; } else if(angleToTarget > Pi / 10){ angle = -5; } else { angle = -1; } } else if (angleToTarget <= TwoPi && angleToTarget > (TwoPi - Pi)) { // Rotate right if(angleToTarget < (TwoPi - Pi / 5)){ angle = 15; } else if(angleToTarget < (TwoPi - Pi / 10)){ angle = 5; } else { angle = 1; } } else if(angleToTarget == 0) { angle = 0; } setRotation(rotation() + angle); // Разворачиваемся // Бежим в сторону мухи if(lineToTarget.length() >= 40){ setPos(mapToParent(0, -(qrand() % ((4 + 1) - 1) + 1))); // Двигаем ножками, Dance, dance, Spidy !!! countForSteps++; if(countForSteps == 6){ steps = 1; update(QRectF(-40,-50,80,100)); } else if (countForSteps == 12){ steps = 0; update(QRectF(-40,-50,80,100)); } else if (countForSteps == 18){ steps = 2; update(QRectF(-40,-50,80,100)); } else if (countForSteps == 24) { steps = 0; update(QRectF(-40,-50,80,100)); countForSteps = 0; } } /* Производим проверку на то, наткнулся ли паук на какой-нибудь * элемент на графической сцене. * Для этого определяем небольшую область перед пауком, * в которой будем искать элементы * */ QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF() << mapToScene(0, 0) << mapToScene(-2, -2) << mapToScene(2, -2)); /* После чего проверяем все элементы. * Один из них будет сам Паук - с ним ничего не делаем. * А с остальными высылаем сигнал в ядро игры * */ foreach (QGraphicsItem *item, foundItems) { if (item == this) continue; if(item == target){ emit signalCheckGameOver(); } } /* Проверка выхода за границы поля * Если объект выходит за заданные границы, то возвращаем его назад * */ 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); // снизу } } /* Функция паузы, отвечает за включение и отключение паузы * */ void Spider::pause() { if(timer->isActive()){ timer->stop(); } else { timer->start(15); } }
віджет.h
У заголовному файлі ядра програми необхідно визначити об'єкт, який відповідає за Павука, змінну стану гри та Гарячу клавішу паузи. А також слоти для обробки запуску гри, паузи та процедури Game Over .
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QGraphicsScene> #include <QGraphicsItem> #include <QShortcut> #include <QDebug> #include <QTimer> #include <QMessageBox> #include <triangle.h> #include <apple.h> #include <spider.h> #define GAME_STOPED 0 #define GAME_STARTED 1 namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private: Ui::Widget *ui; QGraphicsScene *scene; /// Объявляем графическую сцену Triangle *triangle; /// и треугольник QTimer *timer; /** Объявляем игровой таймер, благодаря которому * будет производиться изменения положения объекта на сцене * При воздействии на него клавишами клавиатуры * */ QTimer *timerCreateApple; /// Таймер для периодического создания яблок в игре QList<QGraphicsItem *> apples; /// Список со всеми яблоками, присутствующими в игре double count; /// Переменная, которая хранит счёт игре Spider *spider; // Объект Паука QShortcut *pauseKey; // Горячая клавиша, отвечающая за паузу в игре int gameState; /* Переменная, которая хранит состояние игры. * То есть, если игра запущена, то статус будет GAME_STARTED, * в противном случае GAME_STOPED * */ private slots: /// Слот для удаления яблок если Муха наткнулась на это яблоко void slotDeleteApple(QGraphicsItem * item); void slotCreateApple(); /// Слот для создания яблок, срабатывает по таймеру void on_pushButton_clicked(); // Слот для запуска игры void slotGameOver(); // Слот инициализации Game Over void slotPause(); // Слот для обработки паузы }; #endif // WIDGET_H
widget.cpp
Порівняно з програмним кодом, який був у попередніх уроках, з цим файлом у цьому уроці необхідно ґрунтовно попрацювати. Оскільки гра, стартує натисканням кнопки pushButton, то і програмний код ініціалізації об'єктів та запуску таймерів необхідно винести в слот, якому передається сигнал від кнопки. Також виникає необхідність реалізувати функцію паузи в грі, за якою всі ігрові таймери зупиняються і об'єкти не можуть пересуватися або здійснювати інші дії. А в слоті, який відповідає за Game Over , необхідно правильно обробити видалення всіх об'єктів з ігрової сцени.
#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); this->resize(600,640); /// Задаем размеры виджета, то есть окна this->setFixedSize(600,640); /// Фиксируем размеры виджета scene = new QGraphicsScene(); /// Инициализируем графическую сцену ui->graphicsView->setScene(scene); /// Устанавливаем графическую сцену в graphicsView ui->graphicsView->setRenderHint(QPainter::Antialiasing); /// Устанавливаем сглаживание ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по вертикали ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по горизонтали scene->setSceneRect(-250,-250,500,500); /// Устанавливаем область графической сцены timer = new QTimer(); timerCreateApple = new QTimer(); gameState = GAME_STOPED; pauseKey = new QShortcut(this); pauseKey->setKey(Qt::Key_Pause); connect(pauseKey, &QShortcut::activated, this, &Widget::slotPause); } Widget::~Widget() { delete ui; } void Widget::slotDeleteApple(QGraphicsItem *item) { /* Получив сигнал от Мухи * Перебираем весь список яблок и удаляем найденное яблоко * */ foreach (QGraphicsItem *apple, apples) { if(apple == item){ scene->removeItem(apple); // Удаляем со сцены apples.removeOne(apple); // Удаляем из списка delete apple; // Вообще удаляем ui->lcdNumber->display(count++); /* Увеличиваем счёт на единицу * и отображаем на дисплее * */ } } } void Widget::slotCreateApple() { Apple *apple = new Apple(); // Создаём яблоко scene->addItem(apple); // Помещаем его в сцену со случайной позицией apple->setPos((qrand() % (251)) * ((qrand()%2 == 1)?1:-1), (qrand() % (251)) * ((qrand()%2 == 1)?1:-1)); apple->setZValue(-1); /* Помещаем яблоко ниже Мухи, то есть Муха * на сцене будет выше яблок * */ apples.append(apple); // Добавляем Муху в Список } void Widget::on_pushButton_clicked() { count = 0; ui->lcdNumber->display(count); triangle = new Triangle(); /// Инициализируем муху scene->addItem(triangle); /// Добавляем на сцену треугольник triangle->setPos(0,0); /// Устанавливаем треугольник в центр сцены spider = new Spider(triangle); // Инициализируем паука scene->addItem(spider); // Добавляем паука на сцену spider->setPos(180,180); // Устанавливаем позицию паука /* Подключаем сигнал от паука на проверку состояния GameOver * */ connect(spider, &Spider::signalCheckGameOver, this, &Widget::slotGameOver); /** Инициализируем таймер и вызываем слот обработки сигнала таймера * у Треугольника 100 раз в секунду. * Управляя скоростью отсчётов, соответственно управляем скоростью * изменения состояния графической сцены * */ connect(timer, &QTimer::timeout, triangle, &Triangle::slotGameTimer); timer->start(1000 / 100); /** Раз в секунду отсылаем сигнал на создание яблока в игре * */ connect(timerCreateApple, &QTimer::timeout, this, &Widget::slotCreateApple); timerCreateApple->start(1000); /** Подключаем сигнал от Мухи, в котором передаются Объекты, на которые * наткнулась Муха * */ connect(triangle, &Triangle::signalCheckItem, this, &Widget::slotDeleteApple); ui->pushButton->setEnabled(false); gameState = GAME_STARTED; } void Widget::slotGameOver() { /* Если игра окончена * Отключаем все таймеры * */ timer->stop(); timerCreateApple->stop(); QMessageBox::warning(this, "Game Over", "Мои соболезнования, но Вас только что слопали"); /* Отключаем все сигналы от слотов * */ disconnect(timerCreateApple, &QTimer::timeout, this, &Widget::slotCreateApple); disconnect(triangle, &Triangle::signalCheckItem, this, &Widget::slotDeleteApple); disconnect(spider, &Spider::signalCheckGameOver, this, &Widget::slotGameOver); /* И удаляем все объекты со сцены * */ spider->deleteLater(); triangle->deleteLater(); foreach (QGraphicsItem *apple, apples) { scene->removeItem(apple); // Удаляем со сцены apples.removeOne(apple); // Удаляем из списка delete apple; // Вообще удаляем } /* Активируем кнопку старта игры * */ ui->pushButton->setEnabled(true); gameState = GAME_STOPED; // Устанавливаем состояние игры в GAME_STOPED } void Widget::slotPause() { /* Выполняем проверку на состояние игры, * если игра не запущена, то игнорируем Паузу. * В противном случае или запускаем, или останавливаем все таймеры * */ if(gameState == GAME_STARTED){ if(timer->isActive()){ timer->stop(); timerCreateApple->stop(); spider->pause(); } else { timer->start(1000/100); timerCreateApple->start(1000); spider->pause(); } } }
Підсумок
За результатами проведеної роботи гра перестане бути нескінченною і цінність очок, що набираються, зросте в рази.
Також у відеоуроці за цією статтею подано додаткові коментарі та продемонстровано роботу гри.
Повний список статей цього циклу:
- Урок 1. Як написати гру на Qt. Управління об'єктом
- Урок 2. Як написати гру на Qt. Анімація героя ігри (2D)
- Урок 3. Як написати гру на Qt. Взаємодія з іншими об'єктами
- Урок 4. Як написати гру на Qt. Ворог - сенс у виживанні
- Урок 5. Як написати гру на Qt. Додаємо звук із QMediaPlayer
Здравствуй у меня опять проблема как решить? In member function 'void Widget::on_pushButton_clicked()': ошибка: 'class Ui::Widget' has no member named 'pushButton' ui->pushButton->setEnabled(false); : In member function 'void Widget::slotGameOver()': ошибка: 'class Ui::Widget' has no member named 'pushButton' ui->pushButton->setEnabled(true); скрин http://priscree.ru/img/a9de07f8d2a5a0.jpg ^ ^
вставляю кнопку вообще 10 ошибок не определена ссылка на паука и тд
Добавьте через графический дизайнер кнопку в widget.ui и проследите, чтобы название кнопки было pushButton
добавил кнопку все равно много ошибок вот скрины http://priscree.ru/img/e8e0d04f9e232c.jpg http://priscree.ru/img/3fb040824e8665.jpg
ошибки в vtable... Это moc файлы попортились. Нужно удалить build сборку и пересобрать, и если не поможет, то создать новый проект и переписать в него код. Иначе никак.
хорошо попробую переписать все.как напишу отпишусь)
все заработало спасибо еще вопрос у нас препод просит чтобы мы сделали так.чтобы можно было сохранять результаты это возможно ?
Конечно, возможно. Можно сохранять в файл. Можно сохранять в базу данных. Можно даже в реестр писать с помощью QSettings . Мне больше всего нравится база данных для таких целей. По окончании игры можно добавлять результат в базу данных. Также можно добавить кнопку, которая бы открывала окно с результатами.
а как это сделать ? можете помочь если не сложно конечно и есть время
Значит так. Просто писать код за вас я не буду, но подсказать и направить на нужный путь - это без проблем.
Поэтому -> В предыдущем сообщении я дал ссылку на статью с базой данных. Изучите её. Изучите, как добавлять записи в базу данных. Там же показано, как отобразить записи в таблице. А потом попытайтесь по окончании игры сделать добавление строк в базу данных. В коде есть место, где по окончании игры вызывается диалог. Это происходит в слоте slotGameOver . Вот в нём можно сделать добавление записей в базу данных. Также можете добавить кнопку в окно игры, по нажатию которой можно будет вызвать диалог, в котором как раз и будете показывать рекорды. Если есть затруднения с пониманием сигналов и слотов, то изучите следующую статью .
Здравствуйте,
Подскажите, пжлст, как работает этот код :
Самое непонятное - это 5 -ая строчка, зачем добавлять 90 градусов?
не 90 на 45, ошибся
Там неправильный подсчёт был по направлению и полный бардак был с поведением, эти 45 градусов исправляли ситуацию. Точную причину уже совсем не помню.
А вообще все эти расчёты довольно тяжелы для процессора, я подобрал более лёгкий вариант со скалярными векторами. Но статью ещё пока не недописал к сожалению (она пока в разработке).
Здравствуйте! Почему может быть такая ошибка: враг висит в углу, колеблется в нём, но не двигается? По логике код такой же, но вместо орисовки моделей использую спрайты. Со спрайтом "мухи" всё хорошо, а вот "паук" отказывается вести себя нормально. Подозреваю, что ошибка может быть здесь (это код, который идёт вместо отрисовки):
Или же ошибка где-то в управлении "паука", но там код вообще неизменный. (в Spider.cpp, void Spider...)
Добрый день.
Дело тут явно не в методе paint, скорее всего всё-таки есть какая-то ошибка в другом месте. Обычно такое может быть в районе метода slotGamerTimer
Спасибо! На самом деле ошибка оказалась от невнимательности - просто пропустила подключение движения в сторону модели. Теперь всё работает.
Если вам не сложно, могли бы вы, пожалуйста, объяснить, за что отвечают параметры в скобках тут: setPos(mapToParent(0, -(qrand() % ((2 + 1) - 1) + 1)))?
Нашла только такое, не много о чём говорит: (qrand() % ((high + 1) - low) + low). Экспериментально кое-что выяснила, но хотелось бы знать точно)
А ещё вот тут, когда генерировали бананы: banana->setPos((qrand() % (251)) * ((qrand()%2 == 1)?1:-1),
(qrand() % (251)) * ((qrand()%2 == 1)?1:-1));
А так, благодарю за указку на slotGameTimer!
П.С.: я не очень умею пока читать документацию( поэтому прошу помощи у вас, чтобы разобраться с этими параметрами