- 1. Projektstruktur
- 2. Mauszeiger-Tracking schreiben
- 3. widget.ui
- 4. zollszene.h
- 5. zollszene.cpp
- 6. dreieck.h
- 7. Dreieck.cpp
- 8. widget.h
- 9. widget.cpp
- 10. Ergebnis
- 11. Videoanleitung
Jeder erinnert sich an das Spiel Crimsonland ? Dort galt es, die Monster in Stapeln zu Fall zu bringen. Um sich auf dem Spielfeld zu bewegen, benutzten wir die Tasten W, A, S, D, und für die Schussrichtung Cursor Maus , gefolgt von Tracking. Der Funktionsmechanismus dieser Mausverfolgung an sich ist also äußerst einfach. Es kann viele Implementierungen geben, abhängig von den in der Programmierung verwendeten Werkzeugen, aber wenn wir über die übliche QGraphicsScene sprechen, dann zeige ich Ihnen meine Version der Implementierung von ein solcher Mechanismus.
Projektstruktur
Neben den Hauptdateien verwendet dieses Projekt zwei zusätzliche Klassen. Das erste ist eine benutzerdefinierte QGraphicsScene , die die Position des Cursors verfolgt und Informationen über seine Position überträgt, und das zweite ist die Hauptfigur, unser geliebtes Rotes Dreieck , das wir mit dem * steuern werden * Tasten W. A, S, D. **
Projektstruktur:
- TargetMotion.pro - Projektprofil;
- widget.h - Header-Datei des Hauptanwendungsfensters;
- widget.cpp - Die Quellcodedatei für das Hauptanwendungsfenster;
- Triangle.h - Header-Datei der Hauptfigur Rotes Dreieck;
- Dreieck.cpp - Quellcode-Datei für die Hauptfigur Rotes Dreieck;
- customcene.h - Header-Datei der angepassten Grafikszene;
- customcene.cpp - Die Datei mit den Quellcodes der angepassten Grafikszene;
- cursor.qrc - Ressourcendatei, die den angepassten Mauszeiger enthält.
Mauszeiger-Tracking schreiben
widget.ui
Formulardatei für das Hauptfenster der Anwendung. Wir werfen einfach ein Objekt der Klasse QGraphicsView hinein und strecken es über das gesamte Fenster.
zollszene.h
In dieser Header-Datei müssen lediglich die Methode zum Verfolgen der Mausbewegung mouseMoveEvent () und das Signal, in dem die Koordinaten der Mausposition übertragen werden, deklariert werden, nicht zu vergessen erben die Klasse von 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
zollszene.cpp
Ob Sie es glauben oder nicht, in der Quelldatei rufen wir einfach das Signal mit Koordinaten im mouseMoveEvent auf.
#include "customscene.h" CustomScene::CustomScene(QObject *parent) : QGraphicsScene() { Q_UNUSED(parent); } CustomScene::~CustomScene() { } void CustomScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { emit signalTargetCoordinate(event->scenePos()); }
dreieck.h
Und jetzt werden wir alle Objekte und Funktionen deklarieren, die für das korrekte Rendering unseres Helden und die Implementierung der Cursorverfolgung im Spiel erforderlich sind. Für ein umfassenderes Verständnis der Arbeit dieser Klasse und ihrer Ursprünge empfehle ich Ihnen, sich mit der Artikelserie "Wie man ein Spiel in Qt schreibt" vertraut zu machen, die im Abschnitt [Qt-Tutorials] vorgestellt wird (https://evileg.com/knowledge/qt/) ...
Notiz . WinAPI Bibliotheken werden verwendet, um Schaltflächenklicks zu verarbeiten.
#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
Dreieck.cpp
Das Dreieck wird in diesem Fall einmal gezeichnet, aber die Drehung dieses Objekts und seine Bewegung über die Grafikszene erfolgt periodisch auf ein Signal vom Spieltimer und auf Signale von der Grafikszene über die Cursorbewegung. Bewegt sich der Cursor nicht, sondern nur das Dreieck , dann folgt das Dreieck trotzdem dem Mauszeiger, da das Dreieck Informationen über die letzte Position des Cursors auf der Grafikszene speichert .
#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
Und jetzt müssen Sie alles im Spielkern zusammenbringen, der sich in der Klasse des Hauptanwendungsfensters befindet.
#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
In dieser Datei werden alle Spielobjekte (Grafikszene und Dreieck) deklariert und initialisiert, und es wird ein Entwurfsmuster wie zB Brücke, verwendet, über das das Signal von der Grafikszene zum Dreieck übertragen wird.
#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; }
Ergebnis
Als Ergebnis erhalten Sie ein Dreieck auf der Grafikszene, das sich unter dem Einfluss der W, A, S, D -Tasten um diese herum bewegt und immer in Richtung des Mauszeigers schaut, wenn er sich innerhalb der Grafikszene befindet .
Eine Demonstration dieses Beispiels finden Sie im Video-Tutorial zu diesem Artikel.
А где можно найти полный код проекта (проектов)? У меня ошибки линковки типа ( LNK2019: ссылка на неразрешенный внешний символ "public: void __cdecl Triangle::slotTarget(class QPointF)" (?slotTarget@Triangle@@QEAAXVQPointF@@@Z) в функции "public: __cdecl Widget::Widget(class QWidget *)" ). И может кто подскажет как поправить?
Вот в этой статье в конце есть полный проект.
спасибо
а если например я хочу сделать вместо треугольников - текстурки, я могу это сделать в этом виджете Qt? или надо брать что то другое?
Для отрисовки используется объект класса QPainter методе paint у треугольника. А этот класс QPainter имеет метод drawPixmap(), который может отрисовывать изображение из png файла например. Поэтому да, можете лишь переписать метод paint, и отрисовывать текстуры.
еще такой вопрос. у меня Qt ругается на GetAsyncKeyState (ошибка линковки). Надо либу какую то подключить? 2) а есть такая функция н кросс платформенная (зачем в таком достаточно высокоуровневом фреймворке использовать winApi?)?
Для MSVC пропишите в pro файле проекта следующие строчки
цепочку событий Qt приложения. Что влёчет к значительному усложнению кода, да ещё и может помешать фокусировка на некоторых виджетах, то есть события фокуса ещё вмешиваются. Здесь фокус должен сохраняться на виджете, в котором происходит действие, чтобы корректно отрабатывать нажатия клавиш. В общем много головной боли сразу появляется. Обработка события клавиш в Qt немного неподходит для реализации игровой логики на клавиатуре, это как раз один из тех моментов, когда действительно проще навелосипедить своё... хотя я противник велосипедов, но Qt не даёт нужного функционала в достаточной мере.
я собираю под win64. LIBS += -luser32 и LIBS += -luser64 не помогает
Вы вообще что-то своё уже пишите, или как? Я скачал проект, запустил его на работе на Win64 и он работает.
Да нет. Чет у меня с qt только проблемы. Ваш проект так в qt не смог собрать (из за GetAsyncKeyState). Собрал его в visual studio, но там почему то пришлось в ручную кидать .dll в каталог (хотя путь добавлял) (может тут что изменить см скрин https://yadi.sk/i/-D9CsBhh3SL2eQ ).
Еще пару вопросов по code-style
Честно говоря... не знаю... Я не работаю с Visual Studio вообще. В основном Qt Creator, да иногда с CLion. Больше походит на то, что у вас некорректно установлены пути к библиотекам Qt в операционной системе. Либо сама Visual Studio криво встала.
По code-style
Спасибо за ответы. Еще хотел спросить. Вы занимались программированием сетевых игр? вот например если у нас 2 клиента (у каждого свой треугольник). Как например синхронизируется картинка, сколько раз в секунду передаются действия? просто не оч понимаю как удается избегать задержек и делать плавную картинку
А и по библиотеке. Зачем у qt своя библиотека вещей которые есть в в stl, boost и тд. Например зачем использовать всякие вектора Qt и тд когда они есть в stl? Или это зачем то нужно?
К моему великому сожалению я имею к геймдеву не очень большое отношение, особенно не занимался сетевыми играми, но почитываю информацию об этом в свободное время.
Ммм... здесь за версту отдаёт желанием написать всё своё. Кстати, голос разума у них всё-таки проснулся в Qt 5. Например, для сортировки там используется и рекомендуется уже std::sort , вместо qSort, который они пометили как deprecated. Что касается контейнеров, то у контейнеров Qt есть кое-какие удобные методы. Но последнее время я перехожу на контейнеры stl. Они гораздо функциональнее. Что касается boost, то сигналы и слоты, которые есть в boost, как говорят в интернетах, слизали именно с Qt. Так что все понемножку друг у друга заимствуют.
я переопределил paint треугольника. сделал рисование танчика и его пушки.
разобрался. заменил белый фон на альфаканал. теперь другой вопрос. вот Pixmap пушки я хочу поворачивать вокруг своей оси. можно это сделать както через setRotation например? (тоесть нарисовал корпус, повернул обьект, нарисовал пушку)
Фух и с этим разобрался. Теперь такой вопрос, а можно ли как-то по лучше обрабатывать столкновения обьектов, и чтоб к стене например можно было подъехать в плотную, и при этом не вьехать в ней повернувшись на месте?
Здесь уже нужно обрабатывать очертания танка и не давать возможность ему разворачиваться, если есть риск въехать в стену.
А не подскажите как сделать плавность анимации или как то так. например танк (прямоугольник) едет вверх и вертикален. Потом игрок нажимает клавишу влево и он должен уже ехать вертикально. Можно конечно плавно поворачивать текстуру но я думаю можно сделать лучше. Просто играл в танчики где корпус и пушка могут вращатся и ехать мгновенно на любой градус, и при этом там не было видимого резкого скачка (может это какоето сглаживание?)
Если просто сменить угол поворота или направление, то скачок всегда будет резким. Если хотите добавить некоторую плавность в поворот, то нужно делать поворот за несколько отсчётов игрового таймера, чтобы это было достаточно быстро, но при этом добавило некоторую плавность.
forgetting otnasledovat class from QGraphicsScene :D
Ну да, поленился внимательно посмотреть перевод после Google Translate :D
Добрый день!
Я хотел объединить 2 ваших уроков: "GameDev на Qt" и "Как написать игру на Qt", а именно:
1) Передвижение персонажа и его оружия;
2) Вращение оружия с помощью мыши.
Но чтобы я не делал, у меня уже на постоянной основе 10 внутренних, непонятных мне, ошибок.
Если есть возможность, не могли бы вы мне подсказать, что я делаю не так и как это можно это исправить.
Для удобства скидываю всю программу и скриншот ошибок.
NoNameGames.rar
Добрый день! На сам проект у меня пока нет времени посмотреть, но судя по ошибке, вам нужно добавить макрос Q_OBJECT в triangle и в widget.
Обычно выглядит так
В том то и дело, я добавлял, удалял, миксовал, но всё равно эти ошибки появляются раз за разом.
Если во всех файлах оставить "Q_OBJECT", то появляются данные ошибки:
Здравствуйте! Прохожу 1ю часть GameDev на Qt. Сделал код, однако выдает ошибки. Не подскажете что с этим делать?
Вполне возможно, что ты не закинул graphicsView в дизайнере в виджет
Кому интересно, поворот в slotTarget можно в одну строку организовать
Вполне возможно, что ты не закинул graphicsView в дизайнере в виджет