Evgenii Legotckoi
18 октября 2015 г. 18:54

Qt/C++ - Урок 028. Как использовать sprite картинки с помощью QPixmap

После того, как мы нарисовали sprite в прошлом уроке по работе с Adobe Illustrator, настало время применить полученную картинку при работе с Qt и добавить её в программу с помощью QPixmap. Причем, мы сделаем анимированый sprite, и посмотрим, как происходит маленький анимированый взрыв на графической сцене нашего приложения на Qt.

Структура проекта для работы с QPixmap и sprite

Структура проекта sprite_example будет следующая:

  • sprite_example.pro - профайл проект;
  • widget.h - заголовочный файл основного окна приложения;
  • widget.cpp - файл исходных кодов основного окна приложения;
  • widget.ui - файл интерфейса;
  • sprite.h - заголовочный файл класса, предназначенного для нашего спрайта, в котором будет применяться QPixmap;
  • sprite.cpp - файл исходных кодов для работы с QPixmap;
  • sprite.qrc - ресурсный файл;
  • sprite_sheet.png - наш спрайт, который мы применим для анимации.

widget.ui

Ничего особенного в этом файле нет. Просто растягиваем по всему окно объект QGraphicsView, в который поместим QGraphicsScene .

widget.h

В этом файле мы объявим объект графической сцены, в который поместим объект с QPixmap, в котором будет анимированый sprite. Только не забудьте подключить заголовочный файл sprite.h.

  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3.  
  4. #include <QWidget>
  5. #include <QGraphicsScene>
  6. #include <QTimer>
  7. #include <QList>
  8. #include <QPixmap>
  9.  
  10. #include "sprite.h"
  11.  
  12. namespace Ui {
  13. class Widget;
  14. }
  15.  
  16. class Widget : public QWidget
  17. {
  18. Q_OBJECT
  19.  
  20. public:
  21. explicit Widget(QWidget *parent = 0);
  22. ~Widget();
  23.  
  24. private:
  25. Ui::Widget *ui;
  26. QGraphicsScene *scene; // Объявляем графическую сцену
  27. };
  28.  
  29. #endif // WIDGET_H

widget.cpp

В данном файле инициализируем графическую сцену и помещаем её в graphicsView, а также создаём объект спрайта и помещаем его на сцену.

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3.  
  4. Widget::Widget(QWidget *parent) :
  5. QWidget(parent),
  6. ui(new Ui::Widget)
  7. {
  8. ui->setupUi(this);
  9.  
  10. scene = new QGraphicsScene(); // Инициализируем графическую сцену
  11.  
  12. ui->graphicsView->setScene(scene); // Устанавливаем графическую сцену в graphicsView
  13.  
  14. scene->addItem(new Sprite()); // Помещаем на сцену новый объект спрайта
  15.  
  16. }
  17.  
  18. Widget::~Widget()
  19. {
  20. delete ui;
  21. }

sprite.h

А вот теперь поговорим о том, как же реализован анимированый sprite. Для этого нам понадобится заранее подготовленный спрайт, а вернее sprite sheet, который состоит из 15 кадров, расположенных в одну строку.

Технически, данный sprite sheet устанавливается в объект класса QPixmap, в котором он пролистывается слева направо с определенной периодичность. Для этого мы новый класс наследуем от QGraphicsItem и QObject, чтобы использовать сигналы и слоты . И в методе paint задаём отрисовку участка изображения sprite_sheet , который задан в ресурсном файле. Таймер управляет сменой кадров с помощью слота nextFrame(). По сигналу от таймера мы изменяем координату отрисовки участка спрайта и перерисовываем графический объект с новым кадром.

  1. #ifndef SPRITE_H
  2. #define SPRITE_H
  3.  
  4. #include <QObject>
  5. #include <QGraphicsItem>
  6. #include <QTimer>
  7. #include <QPixmap>
  8. #include <QPainter>
  9.  
  10. class Sprite : public QObject, public QGraphicsItem
  11. {
  12. Q_OBJECT
  13. public:
  14. explicit Sprite(QObject *parent = 0);
  15.  
  16. signals:
  17.  
  18. public slots:
  19.  
  20. private slots:
  21. void nextFrame(); // Слот для пролистывания изображения в QPixmap
  22.  
  23. private:
  24. void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  25. QRectF boundingRect() const;
  26.  
  27. private:
  28. QTimer *timer; // Таймер для пролистывания изображения в QPixmap
  29. QPixmap *spriteImage; // В данный объект QPixamp будет помещён спрайт
  30. int currentFrame; // Координата X, с которой начинает очередной кадр спрайта
  31.  
  32. };
  33.  
  34. #endif // SPRITE_H

sprite.cpp

В конструкторе класса проводим инициализацию таймера, а в методе paint отрисовываем нужный нам кадр из спрайта. Переключение кадров производится с помощью слота nextFrame().

  1. #include "sprite.h"
  2.  
  3. Sprite::Sprite(QObject *parent) :
  4. QObject(parent), QGraphicsItem()
  5. {
  6. currentFrame = 0; // Устанавливаем координату текущего кадра спрайта
  7. spriteImage = new QPixmap(":sprite_sheet.png"); // Загружаем изображение спрайта в QPixmap
  8.  
  9. timer = new QTimer(); // Создаём таймер для анимации спрайта
  10. // Подключаем сигнал от таймера к слоту перелистывания кадров спрайта
  11. connect(timer, &QTimer::timeout, this, &Sprite::nextFrame);
  12. timer->start(25); // Запускаем спрайт на генерацию сигнала с периодичность 25 мс
  13. }
  14.  
  15. QRectF Sprite::boundingRect() const
  16. {
  17. return QRectF(-10,-10,20,20);
  18. }
  19.  
  20. void Sprite::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  21. {
  22. /* В отрисовщике графического объекта отрисовываем спрайт
  23. * Разберём все параметры данной функции
  24. * Первых два аргумента - это координат X и Y куда помещается QPixmap
  25. * Третий аргумент - это указатель на QPixmap
  26. * 4 и 5 аргументы - Координаты в В изображении QPixmap, откуда будет отображаться изображение
  27. * Задавая координату X с помощью перемнной currentFrame мы будем как бы передвигать камеру
  28. * по спрайту
  29. * и последние два аргумента - это ширина и высота отображаем части изображение, то есть кадра
  30. * */
  31. painter->drawPixmap(-10,-10, *spriteImage, currentFrame, 0, 20,20);
  32. Q_UNUSED(option);
  33. Q_UNUSED(widget);
  34. }
  35.  
  36. void Sprite::nextFrame()
  37. {
  38. /* По сигналу от таймера передвигаем на 20 пикселей точку отрисовки
  39. * Если currentFrame = 300 то обнуляем его, поскольку размер sprite sheet 300 пикселей на 20
  40. * */
  41. currentFrame += 20;
  42. if (currentFrame >= 300 ) currentFrame = 0;
  43. this->update(-10,-10,20,20); // и перерисовываем графический объект с новым кадром спрайта
  44. }

Итог

В результате у Вас получится приложение, в середине окна которого будет находится моргающий взрыв размером 20 на 20 пикселей. Демонстрацию работы приложения Вы можете увидеть в видеоуроке.

Видеоурок

Рекомендуемые статьи по этой тематике

По статье задано0вопрос(ов)

4

Вам это нравится? Поделитесь в социальных сетях!

V
  • 12 декабря 2018 г. 1:25

Подскажите пожалуйста ( я новичок совсем)
Можно ли организовать спрайт без этого окошка (как в fl studio fruity dance)?

Evgenii Legotckoi
  • 12 декабря 2018 г. 3:01

Не знаю, какой-там конкретно эффект и если честно не хочется fl studio ради того, чтобы посмотреть устанавливать, но из того, что увидел в интернете.

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

Вот в этой статье было что-то похожее. Там я отключал обрамление окна и кастомизировал виджеты.

Обратите внимание вот на эти строчки

  1. this->setWindowFlags(Qt::FramelessWindowHint); // Отключаем оформление окна
  2. this->setAttribute(Qt::WA_TranslucentBackground); // Делаем фон главного виджета прозрачным
V
  • 15 декабря 2018 г. 8:06

Спасибо большое!
Очень помогли!

V
  • 22 декабря 2018 г. 23:30

А возможно всё кроме спрайта сделать прозрачным?

Evgenii Legotckoi
  • 23 декабря 2018 г. 0:53

"всё" - это что?

по факту я вам уже ответил и на этот вопрос.

V
  • 23 декабря 2018 г. 1:58

Извините, но дилемма в том что - Спрайт должен быть сам по себе без заднего окна и виджета. ( вот то что я хочу сделать-

Прошу прощения за надоедливые и банальные вопросы

Evgenii Legotckoi
  • 24 декабря 2018 г. 3:28

Прошу прощения за надоедливые и банальные вопросы

Да не в этом дело.

По сути, там всё тоже самое, только отображайте спрайт в отдельном диалоговом окне, у которого будет полностью отключено обрамление и т.д. Просто используйте QDialog, чтобы спрайт отдельно крутился.

V
  • 14 января 2019 г. 15:23

С Новым годом!
Всё оказалось проще:
MAIN.cpp

  1. #include <QGuiApplication>
  2. #include <QQmlApplicationEngine>
  3.  
  4. #include <QtGui/QGuiApplication>
  5. #include <QScreen>
  6.  
  7.  
  8.  
  9. int main(int argc, char *argv[])
  10. {
  11.  
  12. QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
  13.  
  14. QGuiApplication app(argc, argv);
  15.  
  16. QQmlApplicationEngine engine;
  17. engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
  18. if (engine.rootObjects().isEmpty())
  19. return -1;
  20.  
  21.  
  22.  
  23. return app.exec();
  24. }
  25.  
  26.  
  27.  

QML

  1.  
  2. import QtQuick 2.0
  3. import QtQuick.Window 2.2
  4.  
  5. Window {
  6. flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WA_TintedBackground |Qt.WindowStaysOnTopHint
  7.  
  8. color: "#00000000"
  9.  
  10.  
  11. visible: true
  12. width: 100
  13. height: 460
  14. x: (Screen.width - width)
  15. y: (Screen.height - height)
  16.  
  17.  
  18.  
  19.  
  20. Rectangle {
  21. anchors.fill: parent
  22. color: "transparent"
  23. }
  24. AnimatedSprite {
  25.  
  26. id: sprite;
  27. width: 100;
  28. height: 100;
  29. anchors.centerIn: parent;
  30. source: "Donald.png"
  31. frameX: 0
  32. frameY: 0
  33. frameRate: 18;
  34. frameWidth: 100
  35. frameHeight: 100
  36. frameCount: 24
  37. running: folse;

У меня всего 2 вопроса:
\\
Как В файле QML менять значение с помощью переменной из Main frameX: 0 не через класс? ( желательно считывание из txt файла)
\\\
Как сделать так чтобы курсор мышки не реагировал на прозрачное диалоговое окно и я мог спокойно нажимать на ярлыки позади него

Evgenii Legotckoi
  • 15 января 2019 г. 21:09

Ну вы здесь тоже самое сделали, что я вам и советовал... только ещё QML за уши притянули.

frameX - только через класс устанавливать. В QML все объекты наследованы от QObject и там работа с классами идёт. Максимум использовать JavaScript встроенный в QML, но опять же без C++ и классов ничего вы не сделаете.

Как сделать так чтобы курсор мышки не реагировал на прозрачное диалоговое окно и я мог спокойно нажимать на ярлыки позади него

Это было бы проще реализовать через классические виджеты, также нужно лезть в API операционной системы. Я такое не делал, небоходимости не было. Могу сказать только, что нужно искать информацию по эмулированию кликов, чтобы перекинуть клик за окно приложения. В общем в три строки эо не делается.

Планомерно изучайте Qt, с ростом опыта разберётесь, как это сделать, но я могу вам только общее направление по этой задаче подсказать и кое-какие примеры, но вопросы у вас должны быть более гранулированными, а не так, "как написать программу, которая сделает то, что вы хотите". Серьёзно, для того, что вам нужно, требуется изучить больше, чем вот эта одна строка из вашего кода

flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WA_TintedBackground |Qt.WindowStaysOnTopHint

значительно больше

ЯК
  • 25 декабря 2020 г. 20:03

А как вы сделали папку ресурсы ?

Комментарии

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