Реклама

GameDev на Qt - Урок 1. Отслеживание перемещения мыши в QGraphicsScene

TutorialQtGameDev, QGraphicsScene, Qt, отслеживание, cursor, mouse759

Все помнят игру Crimsonland? Там нужно было валить монстров штабелями. Для перемещения по игровому полю мы использовали клавиши W, A, S, D, а для направления стрельбы курсор мыши, за которым производилось отслеживание. Так вот, сам по себе механизм работы этого самого отслеживания мыши предельно прост. Реализаций может быть множество, в зависимости от используемых инструментов в программировании, но если говорить об обычном QGraphicsScene, то позвольте продемонстрировать Вам мой вариант реализации подобного механизма.

Структура проекта

В этом проекте помимо основных файлов используется два дополнительных класса. Первый - это кастомизированный QGraphicsScene, который будет производить отслеживание положения курсора и передавать информацию о его положении, а второй - это главный герой, наш любимый Красный Треугольник, которым мы будем управлять с помощью клавиш W, A, S, D.

Структура проекта:

  • TargetMotion.pro -  Профайл проекта;
  • widget.h - Заголовочный файл основного окна приложения;
  • widget.cpp - Файл исходных кодов основного окна приложения;
  • triangle.h - Заголовочный файл главного героя Красного Треугольника;
  • triangle.cpp - Файл исходных кодов главного героя Красного Треугольника;
  • customscene.h - Заголовочный файл кастомизированной графической сцены;
  • customscene.cpp - Файл исходных кодов кастомизированной графической сцены;
  • cursor.qrc - файл ресурсов, в котором содержится кастомизированный курсор мыши.

Пишем отслеживание курсора мыши

widget.ui

Файл формы главного окна приложения. В него лишь закидываем объект класса QGraphicsView и растягиваем его по всему окну.

customscene.h

Всё, что нужно сделать в данном заголовочном файле, это лишь объявить метод для отслеживания перемещения мыши mouseMoveEvent() и сигнал, в котором будут передаваться координаты положения мыши, не забывая отнаследовать класс от QGraphicsScene.

#ifndef CUSTOMSCENE_H
#define CUSTOMSCENE_H

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

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

signals:
    // Сигнал для передачи координат положения курсора мыши
    void signalTargetCoordinate(QPointF point);

public slots:

private:
    // Функция, в которой производится отслеживание положения мыши
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
};

#endif // CUSTOMSCENE_H

customscene.cpp

Вы не поверите, но в файле исходных кодов Мы всего лишь вызываем сигнал с координатами в методе mouseMoveEvent.

#include "customscene.h"

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

CustomScene::~CustomScene()
{

}

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

triangle.h

А теперь объявим все объекты и функции, необходимые для правильной отрисовки нашего героя и реализации отслеживания курсора в игре. Для более полного представления о работе данного класса и его истоков рекомендую ознакомиться с циклом статей "Как написать игру на Qt", который представлен в разделе Qt Уроки.

Примечание. Для обработки нажатий кнопок используются библиотеки WinAPI.

#ifndef TRIANGLE_H
#define TRIANGLE_H

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

#include <windows.h>

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

signals:

public slots:
    // Слот для получения данных о положении курсора
    void slotTarget(QPointF point);

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

private slots:
    void slotGameTimer();   // Игровой слот

private:
    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);      // Устанавливаем исходный разворот треугольника
    gameTimer = new QTimer();   // Инициализируем игровой таймер
    // Подключаем сигнал от таймера и слоту обработки игрового таймера
    connect(gameTimer, &QTimer::timeout, this, &Triangle::slotGameTimer);
    gameTimer->start(10);   // Стартуем таймер
    target = QPointF(0,0);  // Устанавливаем изначальное положение курсора
}

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)
{
    // Определяем расстояние до цели
    target = point;
    QLineF lineToTarget(QPointF(0, 0), mapFromScene(target));
    // Угол поворота в направлении к цели
    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
        setRotation(rotation() - angleToTarget * 180 /Pi);
    } else if (angleToTarget <= TwoPi && angleToTarget > Pi) {
        // Rotate right
        setRotation(rotation() + (angleToTarget - TwoPi )* (-180) /Pi);
    }
}

void Triangle::slotGameTimer()
{
    /* Перемещаем треугольник в зависимости от нажатых кнопок
     * */
    if(GetAsyncKeyState('A')){
        this->setX(this->x() - 2);
    }
    if(GetAsyncKeyState('D')){
        this->setX(this->x() + 2);
    }
    if(GetAsyncKeyState('W')){
        this->setY(this->y() - 2);
    }
    if(GetAsyncKeyState('S')){
        this->setY(this->y() + 2);
    }

    /* Проверка выхода за границы поля
     * Если объект выходит за заданные границы, то возвращаем его назад
     * */
    if(this->x() - 30 < 0){
        this->setX(30);         // слева
    }
    if(this->x() + 30 > 500){
        this->setX(500 - 30);   // справа
    }

    if(this->y() - 30 < 0){
        this->setY(30);         // сверху
    }
    if(this->y() + 30 > 500){
        this->setY(500 - 30);   // снизу
    }

    QLineF lineToTarget(QPointF(0, 0), mapFromScene(target));
    // Угол поворота в направлении к цели
    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
        setRotation(rotation() - angleToTarget * 180 /Pi);
    } else if (angleToTarget <= TwoPi && angleToTarget > Pi) {
        // Rotate right
        setRotation(rotation() + (angleToTarget - TwoPi )* (-180) /Pi);
    }
}

widget.h

А теперь необходимо всё свести в игровом ядре, которое будет находится в классе главного окна приложения.

#ifndef WIDGET_H
#define WIDGET_H

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

#include <triangle.h>
#include <customscene.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;     // Объявляем треугольник
};

#endif // WIDGET_H

widget.cpp

В данном файле объявляются и инициализируются все игровые объекты (графическая сцена и треугольник), а также используется паттерн проектирования типа Мост, через который передаётся сигнал от графической сцены к треугольнику.

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

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    // Устанавливаем параметры окна приложения
    this->resize(600,600);
    this->setFixedSize(600,600);

    ui->setupUi(this);
    scene   = new CustomScene();    // Инициализируем кастомизированную сцену

    ui->graphicsView->setScene(scene);  /// Устанавливаем графическую сцену в graphicsView
    ui->graphicsView->setRenderHint(QPainter::Antialiasing);    /// Устанавливаем сглаживание
    ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по вертикали
    ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /// Отключаем скроллбар по горизонтали

    scene->setSceneRect(0,0,500,500);   // Устанавливаем размеры графической сцены

    // Создаем кастомизированный курсор из ресурсного файла
    QCursor cursor = QCursor(QPixmap(":/cursor/cursorTarget.png"));
    ui->graphicsView->setCursor(cursor);    // Устанавливаем курсор в QGraphicsView
    triangle = new Triangle();  // Инициализируем треугольник
    triangle->setPos(250,250);  // Устанавливаем стартовую позицию треугольника
    scene->addItem(triangle);   // Добавляем треугольник на графическую сцену

    /* Разрешаем отслеживание положение курсора мыши
     * без необходимости нажатия на кнопки мыши
     * Применяем это свойство именно для QGraphicsView,
     * в котором установлена графическая сцена
     * */
    ui->graphicsView->setMouseTracking(true);

    // Подключаем сигнал от графической сцены к слоту треугольника
    connect(scene, &CustomScene::signalTargetCoordinate, triangle, &Triangle::slotTarget);
}

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

Итог

В результате вы получите треугольник на графической сцене, который будет перемещаться по ней под воздействием клавиш W, A, S, D и всегда смотреть в сторону курсора мыши, если тот находится в пределах графической сцены.

Демонстрация данного примера приведена в видеоуроке по данной статье.

Видеоурок

 

@EVILEG 28 сентября 2015 г. 22:29

Реклама

Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь

Реклама

Реклама