Privacy policyContactsAbout siteOpinionsGitHubDonate
© EVILEG 2015-2018
Recommend hosting
TIMEWEB

GameDev on Qt - Tutorial 2. Bullet Writing class for shooting in Qt

GameDev, bullet, Qt, пуля

Once we started to manage their hero, and his gaze is always turned toward the goal, it's time to write a class of Bullet, which will be responsible for the bullets and their flight on the gaming scene. The mechanics of moving a bullet on the graphic scene will be similar to the mechanics of movement of the main character. The difference is that the bullet will always move in a straight line and turn the bullets need to be set only at the time of the creation of Bullet class object, to set the direction of flight of the bullet.

Project structure

The structure of the project from the previous lesson is changed in the sense that a new class is added Bullet. You also need to modify all of the other classes to ensure interaction with the new class.

So we start in order, namely, the class which is initialized to the event causing the firing process.

customscene.h

The customized graphics scene will need to add a function to handle a mouse click ( mousePressEvent and mouseReleaseEvent ), as well as a signal for transmission permission for shooting from the graphic scene, the main character. Of course, check the mouse press could be in the class of the hero, but the problem is that you need to allow shooting only in the field of graphic scenes, because it it can be other interactive elements that should not cause a fire event.

#ifndef CUSTOMSCENE_H
#define CUSTOMSCENE_H

#include <QObject>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>

class CustomScene : public QGraphicsScene
{
    Q_OBJECT
public:
    explicit CustomScene(QObject *parent = 0);
    ~CustomScene();

signals:
    // The signal to transmit the coordinates of the mouse position
    void signalTargetCoordinate(QPointF point);
    void signalShot(bool shot); // Сигнал на стрельбу

public slots:

private:
    // The function, which made tracking the mouse
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
};

#endif // CUSTOMSCENE_H

customscene.cpp

#include "customscene.h"

CustomScene::CustomScene(QObject *parent) :
    QGraphicsScene()
{
    Q_UNUSED(parent);
}

CustomScene::~CustomScene()
{

}

void CustomScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    emit signalTargetCoordinate(event->scenePos());
}

void CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    emit signalShot(true); // When the mouse button is pressed, it is possible to shoot
    Q_UNUSED(event);
}

void CustomScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    emit signalShot(false); // When the mouse button was released, you can not shoot
    Q_UNUSED(event);
}

widget.h

To transfer data to permit the firing connect signalShot of graphic scene to slotShot in the triangle. In it will be sent information on whether the mouse button is clicked in the graphic scene. Also in the triangle is passed signalBullet, which triggers the creation of a bullet in the core of the game, in the Widget class. In slotBullet slot created Bullet class object and is set on a graphic scene.

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QGraphicsScene>
#include <QGraphicsItem>

#include <triangle.h>
#include <customscene.h>
#include <bullet.h>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private:
    Ui::Widget *ui;
    CustomScene  *scene;    
    Triangle *triangle;     

private slots:
    void slotBullet(QPointF start, QPointF end);
};

#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    /* Source code from previous lesson
     */
    connect(scene, &CustomScene::signalShot, triangle, &Triangle::slotShot);
    connect(triangle, &Triangle::signalBullet, this, &Widget::slotBullet);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::slotBullet(QPointF start, QPointF end)
{
    // Добавляем на сцену пулю
    scene->addItem(new Bullet(start, end));
}

triangle.h

In the class of the hero, you must create a new timer, which will trigger the creation of a new bullet 6 times per second, if the signal was received from the graphic scene on fire resolution. Once received a signal to stop the shooting, the creation of bullets ignored.

#ifndef TRIANGLE_H
#define TRIANGLE_H

#include <QObject>
#include <QGraphicsItem>
#include <QPainter>
#include <QPolygon>
#include <QTimer>
#include <QDebug>
#include <QCursor>

#include <windows.h>

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

signals:
    // Signal to produce a bullet trajectory parameters
    void signalBullet(QPointF start, QPointF end);

public slots:
    // The slot for the cursor position data
    void slotTarget(QPointF point);
    // slot for processing the shooting permission
    void slotShot(bool shot);

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

private slots:
    void slotGameTimer();   
    void slotBulletTimer(); 

private:
    bool shot;              
    QTimer *bulletTimer;    
    QTimer *gameTimer;     
    QPointF target;        
};

#endif // TRIANGLE_H

triangle.cpp

#include "triangle.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;
}

Triangle::Triangle(QObject *parent) :
    QObject(parent), QGraphicsItem()
{
    setRotation(0);      

    target = QPointF(0,0); 
    shot = false;

    gameTimer = new QTimer();   // Init Game Timer
    // Connect the signal from the timer and the slot game processing timer
    connect(gameTimer, &QTimer::timeout, this, &Triangle::slotGameTimer);
    gameTimer->start(10);   // Start Timer

    bulletTimer = new QTimer(); // Init timer of bullets creation
    connect(bulletTimer, &QTimer::timeout, this, &Triangle::slotBulletTimer);
    bulletTimer->start(1000/6); // Shot 6 times per second


}

Triangle::~Triangle()
{

}

QRectF Triangle::boundingRect() const
{
    return QRectF(-20,-30,40,60);
}

void Triangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QPolygon polygon;
    polygon << QPoint(0,-30) << QPoint(20,30) << QPoint(-20,30);
    painter->setBrush(Qt::red);
    painter->drawPolygon(polygon);

    Q_UNUSED(option);
    Q_UNUSED(widget);
}

void Triangle::slotTarget(QPointF point)
{
    /* Source code from previous lesson */
}

void Triangle::slotGameTimer()
{
   /* Source code from previous lesson */
}

void Triangle::slotBulletTimer()
{
    // If shooting is enabled, the call signal to the creation of a bullet
    if(shot) emit signalBullet(QPointF(this->x(),this->y()), target);

}

void Triangle::slotShot(bool shot)
{
    this->shot = shot;  // We get a permit or deny for shooting
}

bullet.h

Now let's create the most by a bullet Bullet class. This class is inherited from QGraphicsItem and working with it is similar to the work produced with the main character. When you exit the bullet out of the playing field, it must be destroyed to release the program memory.

#ifndef BULLET_H
#define BULLET_H

#include <QObject>
#include <QGraphicsItem>
#include <QTimer>
#include <QPainter>

class Bullet : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    explicit Bullet(QPointF start, QPointF end, QObject *parent = 0);
    ~Bullet();

signals:


public slots:

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

private:
    QTimer *timerBullet;    

private slots:
    void slotTimerBullet(); 
};

#endif // BULLET_H

bullet.cpp

We send two objects QPointF onto the bullet constructor, which define the trajectory of the bullet flight. The first coordinate is - a place of departure bullets, the place where it was initiated by the establishment, and the second coordinate - it is the location of the cursor at the time of the shot. For these two coordinates given by the trajectory of its flight.

Game Bullet timer ticks by initiating the launch of the game slot in which the ball is moving forward and 10 pixels in case of the playing field is destroyed.

#include "bullet.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;
}

Bullet::Bullet(QPointF start, QPointF end, QObject *parent)
    : QObject(parent), QGraphicsItem()
{
    this->setRotation(0);   // Set start rotation
    this->setPos(start);    // Set start position
    // Determine the trajectory of the bullet flight
    QLineF lineToTarget(start, end);
    // The angle of rotation in the direction to the target
    qreal angleToTarget = ::acos(lineToTarget.dx() / lineToTarget.length());
    if (lineToTarget.dy() < 0)
        angleToTarget = TwoPi - angleToTarget;
    angleToTarget = normalizeAngle((Pi - angleToTarget) + Pi / 2);

    /* Expand bullet trajectory
     * */
    if (angleToTarget >= 0 && angleToTarget < Pi) {
        /// Rotate left
        setRotation(rotation() - angleToTarget * 180 /Pi);
    } else if (angleToTarget <= TwoPi && angleToTarget > Pi) {
        /// Rotate right
        setRotation(rotation() + (angleToTarget - TwoPi )* (-180) /Pi);
    }
    // Initialize Flight Timer
    timerBullet = new QTimer();
    connect(timerBullet, &QTimer::timeout, this, &Bullet::slotTimerBullet);
    timerBullet->start(7);
}

Bullet::~Bullet()
{

}

QRectF Bullet::boundingRect() const
{
    return QRectF(0,0,2,4);
}

void Bullet::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setPen(Qt::black);
    painter->setBrush(Qt::black);
    painter->drawRect(0,0,2,4);

    Q_UNUSED(option);
    Q_UNUSED(widget);
}

void Bullet::slotTimerBullet()
{
    setPos(mapToParent(0, -10));

    /* Check the output of the field boundary
     * If the bullet flies over the specified limits the bullet must be destroyed
     * */
    if(this->x() < 0){
        this->deleteLater();
    }
    if(this->x() > 500){
        this->deleteLater();
    }

    if(this->y() < 0){
        this->deleteLater();
    }
    if(this->y() > 500){
        this->deleteLater();
    }
}

Result

As a result, our protagonist has got the ability to shoot. What is particularly shown in the video tutorial on this article.

Video

k

почему-то при компиляции ругается на Bullet тут

  1. void Widget::slotBullet(QPointF start, QPointF end)
  2. {
  3. // Добавляем на сцену пулю
  4. scene->addItem(new Bullet(start, end));
  5. }
ошибка: invalid new-expression of abstract class type 'Bullet'
Почему компилятор считает его абстрактным?
Qt 5.10, компилятор minGW 5.3.0

Вы переопределили методы paint и boundingRect?

И возможно, что требуется переопределить ещё какие-то методы в Qt 5.10. Посмотрите, что там есть из виртуальных методов у базового класса bullet.
Конкретно посмотрите методы которые приравнены нулю, например, с такой записью
virtual void someMethod() = 0;
k

Все оказалось куда проще: не поставил const перед QStyleOptionGraphicsItem *option как в заголовочном файле, так и в фале исходного кода.

Зато получше познакомился с интерфейсом Qt Creator, пока следовал вашему совету, очень удобный. Спасибо :)

Наиболее удобный интерфейс и управление у CLion, однако он не имеет поддержки синтаксиса и макросов Qt, что очень сильно тормозит скорость разработки, по сравнению с Qt Creator, а также абсолютно нет поддержки QML, кроме синтаксиса с помощью плагина.

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