Privacy policyContactsAbout siteOpinionsGitHubDonate
© EVILEG 2015-2018
Recommend hosting
TIMEWEB

How to make game using Qt - Lesson 4. Enemy – meaning in the survival

GameDev, QGraphicsItem, QGraphicsScene, QPainter, Qt, написать игру

Continuing the theme of how to write a play on Qt. Once in past articles a fly was created, that eating apples, it is time to create her enemy. And the enemy flies, as is well known, the spider. Creation of game characters, who will participate in the immediate life of your main character - is not only rendering the animation action and movement, as well as the reactions of the logic on the impact of the player, but also artificial intelligence, in accordance with the logic of which will be determined by the behavior of the game character. Thus, we add to the game a new meaning, not only to eat as many apples, but to survive at any cost.

We define the behavior of the spider in this game. What should he do? Yes, the most common of all the action - to hunt a fly just chasing it on the playing field.

Also add to the game button to start the game process, and pause, and the most important thing to add - it's Game Over.

The enemy flies in the project structure

As is the case with the fly in the structure of the project added an additional class, which will be responsible for the object, which is a spider.

  • spider.h - header file of spider
  • spider.cpp - source file of spider

spider.h

The difference between this file from the file header flies is that it declared the game timer, which is responsible for the behavior of the Spider, the enemy of Flies is clocked by its own internal clock. Also during initialization spider lays its purpose in him, that is, fly, for which he should be relentless. In this case, the artificial intelligence is primitive to ugliness, but more at the moment and is not required.

#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();   // Init of pause

signals:
    void signalCheckGameOver();  // Game Over signal

public slots:

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

private:
    qreal angle;        // The angle of rotation of the graphic object
    int steps;          // Number position spider legs
    int countForSteps;  // Counter to change the position of the legs
    QTimer      *timer;     // Internal timer of spider
    QGraphicsItem * target; // The purpose of the spider, this object is equal Flies object

private slots:
    void slotGameTimer();   // Slot of game timer
};

#endif // SPIDER_H

spider.cpp

The source code of Spider is similar to source code of Flies, with the difference that in the player slots Spider follow of Flies on the board, and spider turns to the side and flies crawling on her. And as soon as the enemy flies stumbles on some of the objects on the graphic scene, then checks whether the object Mucha, if so, what procedure is initialized 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);     // Set the angle of rotation of the graphic object

    this->target = target;  // We set the goal of spider

    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){     // The first position of the legs
        // 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){     // The second position of the legs
        // 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){     // The third position of the legs
        // 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));
    // The left mandibles
    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);

    // The right mandibles
    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);

    // head
    painter->setBrush(QColor(146, 115, 40, 255));
    painter->drawEllipse(-10,-25,20,15);
    // Body
    painter->drawEllipse(-15, -15, 30, 30);
    // Back
    painter->drawEllipse(-20, 0, 40,50);
    painter->setPen(QPen(Qt::white,3));
    painter->drawLine(-10,25,10,25);
    painter->drawLine(0,35,0,15);
    // Left eye
    painter->setPen(QPen(Qt::black,1));
    painter->setBrush(Qt::red);
    painter->drawEllipse(-8,-23,6,8);
    // Right eye
    painter->setBrush(Qt::red);
    painter->drawEllipse(2,-23,6,8);

    Q_UNUSED(option);
    Q_UNUSED(widget);
}

void Spider::slotGameTimer()
{
    // Determine the distance to Fly
    QLineF lineToTarget(QPointF(0, 0), mapFromItem(target, 0, 0));
    // The angle of rotation in the direction of Fly
    qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length());
    if (lineToTarget.dy() < 0)
        angleToTarget = TwoPi - angleToTarget;
    angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2);

    /* Depending on whether the left or the right is from the Fly spider, 
     * set the direction of rotation of the spider in the timer tick. 
     * The speed of rotation depends on the angle at which you must turn the spider
     * */
    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); // Turned

    // Бежим в сторону мухи
    if(lineToTarget.length() >= 40){
        setPos(mapToParent(0, -(qrand() % ((4 + 1) - 1) + 1)));

        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;
        }
    }

    /* Checks for whether the spider came on any element in the graphic scene.
     * */
    QList<QGraphicsItem *> foundItems = scene()->items(QPolygonF()
                                                           << mapToScene(0, 0)
                                                           << mapToScene(-2, -2)
                                                           << mapToScene(2, -2));
    /* Check elements. Try to find fly
     * */
    foreach (QGraphicsItem *item, foundItems) {
        if (item == this)
            continue;
        if(item == target){
            emit signalCheckGameOver();
        }
    }

    /* Check the output of the field boundary
     * If the subject is beyond the set boundaries, then return it back
     * */
    if(this->x() - 10 < -250){
        this->setX(-240);       // left
    }
    if(this->x() + 10 > 250){
        this->setX(240);        // right
    }

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

/* Pause function, is responsible for enabling and disabling the pause
 * */
void Spider::pause()
{
    if(timer->isActive()){
        timer->stop();
    } else {
        timer->start(15);
    }
}

widget.h

In the header file, the application kernel to determine the object, which is responsible for the Spider, the variable of the game state,  Pause Hot Key. And a slot for the processing start of the game, pause and Game Over procedures.

#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;     // Game Timer
    QTimer          *timerCreateApple;  /// Timer for periodic 

    QList<QGraphicsItem *> apples;  /// List with all apples
    double count;   /// Scores

    Spider          *spider;        

    QShortcut       *pauseKey;      // Pause hot key
    int             gameState;      

private slots:
    /// The slot for the removal of apples if fly stumbled upon this apple
    void slotDeleteApple(QGraphicsItem * item);
    void slotCreateApple();     /// The slot for the creation of apples, triggered by a timer

    void on_pushButton_clicked();               // The slot for the start of the game
    void slotGameOver();                        // Slot of Game Over
    void slotPause();                           // Slot of Pause
};

#endif // WIDGET_H

widget.cpp

Compared with the program code that has been in previous lessons, with the file in this tutorial, you need to thoroughly work. As the game starts by pressing pushButton button, and the program code object initialization and launch timers need to make in the slot, which is transmitted from the signal button. Also, there is a need to implement a pause function in the game, in which all the game timer is stopped and can not move objects or perform other actions. And in the slot, which is responsible for the Game Over is necessary to properly handle the deletion of all objects with the gaming scene.

#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);  
    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);  // Init Spider
    scene->addItem(spider);         // Add Spider to scene
    spider->setPos(180,180);        // Set Spider position

    connect(spider, &Spider::signalCheckGameOver, this, &Widget::slotGameOver);

    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()
{
    /* If Game Over, then disable timers
     * */
    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;
}

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();
        }
    }
}

Result

According to the results of the work ceases to be a game of infinite value of scores will increase at times.

A full list of articles in this series:

Video

v

Здравствуй у меня опять проблема как решить? 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 ^ ^

v

вставляю кнопку вообще 10 ошибок не определена ссылка на паука и тд

Добавьте через графический дизайнер кнопку в widget.ui и проследите, чтобы название кнопки было pushButton

v

добавил кнопку все равно много ошибок вот скрины http://priscree.ru/img/e8e0d04f9e232c.jpg http://priscree.ru/img/3fb040824e8665.jpg

ошибки в vtable... Это moc файлы попортились. Нужно удалить build сборку и пересобрать, и если не поможет, то создать новый проект и переписать в него код. Иначе никак.

v

хорошо попробую переписать все.как напишу отпишусь)

v

все заработало спасибо еще вопрос у нас препод просит чтобы мы сделали так.чтобы можно было сохранять результаты это возможно ?

Конечно, возможно. Можно сохранять в файл. Можно сохранять в базу данных. Можно даже в реестр писать с помощью QSettings . Мне больше всего нравится база данных для таких целей. По окончании игры можно добавлять результат в базу данных. Также можно добавить кнопку, которая бы открывала окно с результатами.

v

а как это сделать ? можете помочь если не сложно конечно и есть время

Значит так. Просто писать код за вас я не буду, но подсказать и направить на нужный путь - это без проблем.

Поэтому -> В предыдущем сообщении я дал ссылку на статью с базой данных. Изучите её. Изучите, как добавлять записи в базу данных. Там же показано, как отобразить записи в таблице. А потом попытайтесь по окончании игры сделать добавление строк в базу данных. В коде есть место, где по окончании игры вызывается диалог. Это происходит в слоте slotGameOver . Вот в нём можно сделать добавление записей в базу данных. Также можете добавить кнопку в окно игры, по нажатию которой можно будет вызвать диалог, в котором как раз и будете показывать рекорды. Если есть затруднения с пониманием сигналов и слотов, то изучите следующую статью .

M

Здравствуйте,
Подскажите, пжлст, как работает этот код :

  1. QLineF lineToTarget(QPointF(0, 0), mapFromItem(target, 0, 0));  // Проводим линию от паука к мухе
  2. qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length()); // Находим угол. Например 120 гр( 2пи/3 )
  3. if (lineToTarget.dy() < 0)                                             //Если y отрицательный зачем отнимать?
  4. angleToTarget = TwoPi - angleToTarget;                 // ??
  5. angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2); // Тут мы убираем намотку по 2пи. Но зачем добавлять 90 гр?
    Самое непонятное - это 5 -ая строчка, зачем добавлять 90 градусов?

M

не 90 на 45, ошибся

Там неправильный подсчёт был по направлению и полный бардак был с поведением, эти 45 градусов исправляли ситуацию. Точную причину уже совсем не помню.

А вообще все эти расчёты довольно тяжелы для процессора, я подобрал более лёгкий вариант со скалярными векторами. Но статью ещё пока не недописал к сожалению (она пока в разработке).

Comments

Only authorized users can post comments.
Please, Log in or Sign up
P
Feb. 18, 2019, 3:39 p.m.
Poyar

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

  • Result:73points,
  • Rating points1
НБ
Feb. 15, 2019, 1:03 p.m.
Николай Булахтин

C++ - Test 002. Constants

  • Result:25points,
  • Rating points-10
Last comments
V
Feb. 14, 2019, 6:41 p.m.
Vlad15007

Спасибо огромное! Заработало!
А
Feb. 12, 2019, 9:26 a.m.
Александр90

Сам разборался, спасибо.
А
Feb. 12, 2019, 8:19 a.m.
Александр90

День добрый! Можешь выложить форму mainwindow.ui от урока? Заранее спасибо
Feb. 11, 2019, 10:51 a.m.
Евгений Легоцкой

Нет, у меня проблема с жёстким диском случилась, занимался восстановлением ПК, ещё пару вечеров придётся этим заниматься, увы.
Now discuss on the forum
Feb. 17, 2019, 5:28 p.m.
Евгений Легоцкой

Добрый день. Очень извиняюсь за долгий ответ Первое, что нашёл, так это необходимость перерисовать чекбокс. void CheckBoxDelegate::paint(QPainter *painter, const QStyleOptionViewItem ...
Feb. 15, 2019, 3:36 p.m.
Евгений Легоцкой

Ну я тут нашёл одно решение, но сам его не проверял. Вам нужно помещать фамилии скорее всего в ячейки заголовка, и потом просто перерисовывать их QHeaderView * header = m_ui->tableWidget...
Feb. 15, 2019, 7:53 a.m.
Евгений Легоцкой

Добрый день! Не работал с remoteobjects, поэтому глянул документацию, чтобы рассмотреть, что это за зверь. После просмотра документации сложилось стойкой впечатление, что это вполне возм...
m
Feb. 14, 2019, 6:28 p.m.
mr_roman

Нашел решение на Java. Удалось интегрировать в проект сервиса на Qt, теперь из Qt запускаю Java-код акселерометра.
Join us in social networks

For registered users on the site there is a minimum amount of advertising