QGraphicsView图形视图框架使用(六)图元动画
文章目錄
- 創建基本元素
- 使用定時器
- 使用動畫框架
- 調用advance()方法
之前的圖形框架應用中用到的圖元都是靜態的,在實際開發過程中純靜態的圖元肯定是不夠用的,特別是在一些2D游戲類的應用中,很多圖元都是動態的。這里就介紹一下在圖形視圖框架應用中如何實現圖元動畫,圖元動畫主要的實現方法有三種,分別如下:
1.使用定時器
2.使用動畫框架
3.調用Scene的advance()方法
下面以一個例子分別說明一下三種方法如何實現。在實現圖元動畫之前我們先創建一個自定義背景和一個用來執行動畫的圖元。
創建基本元素
用來演示的圖元以一條自定義的魚為例,對應的實現如下:
//fish.h #ifndef Fish_H #define Fish_H#include <QGraphicsPixmapItem>//繼承自圖片圖元的自定義圖元 class Fish : public QGraphicsPixmapItem { public:explicit Fish(QGraphicsItem *parent = 0);//設置圖元在水平和垂直方向上的移動方向void setXDirection(int direction);void setYDirection(int direction);int xDirection();int yDirection();private:int m_Xdirection; //X方向int m_Ydirection; //Y方向 };#endif // Fish_H //fish.cpp #include "fish.h" #include <QPen> #include <QDebug>Fish::Fish(QGraphicsItem *parent): QGraphicsPixmapItem(parent), m_Xdirection(0), m_Ydirection(0) {//添加對應的圖片QPixmap pixmap(":/image/bomb.png");setPixmap(pixmap);//將圖片中心和坐標系中心對齊setOffset(-pixmap.width() / 2, -pixmap.height() / 2); }void Fish::setYDirection(int direction) {m_Ydirection = direction; }int Fish::xDirection() {return m_Xdirection; }int Fish::yDirection() {return m_Ydirection; }void Fish::setXDirection(int direction) {m_Xdirection = direction;if (m_Xdirection != 0) {QTransform transform;//X正方向和負方向的時候鏡像翻轉if (m_Xdirection > 0){transform.scale(-1, 1);}setTransform(transform);} }背景圖元是一個顯示范圍比較大的圖片圖元,對應的實現如下:
//backgrounditem.h #ifndef BACKGROUNDITEM_H #define BACKGROUNDITEM_H#include <QGraphicsPixmapItem> class BackgroundItem : public QGraphicsPixmapItem { public://外部傳進來圖片信息explicit BackgroundItem(const QPixmap &pixmap, QGraphicsItem *parent = 0);public:virtual QPainterPath shape() const; };#endif // BACKGROUNDITEM_H //backgrounditem.cpp #include "backgrounditem.h"BackgroundItem::BackgroundItem(const QPixmap &pixmap, QGraphicsItem * parent): QGraphicsPixmapItem(pixmap, parent) { }QPainterPath BackgroundItem::shape() const {//碰撞檢測的時候是通過shape()來判斷的//通過返回一個空路徑,防止檢測的時候檢測到背景圖片//背景不會和任何圖元發生碰撞return QPainterPath(); }將背景圖元和動畫圖元添加到場景中,對應的實現如下:
//myscene.h #ifndef MYSCENE_H #define MYSCENE_H#include <QGraphicsScene> #include <QTimer>class QGraphicsPixmapItem; class BackgroundItem; class Fish; class MyScene : public QGraphicsScene {Q_OBJECT public:explicit MyScene(QObject *parent = 0);private://初始化背景圖片void initBackgournd();//場景的范圍int m_fieldHeight;int m_fieldWidth;//海洋中的魚Fish* m_fish;BackgroundItem *m_sea; //背景 };#endif // MYSCENE_H //myscene.cpp #include "myscene.h"#include <QKeyEvent> #include <QPropertyAnimation> #include <QGraphicsView> #include <QPen>#include "fish.h" #include "backgrounditem.h" #include <QGamepadManager> #include <QGamepad> #include <math.h>MyScene::MyScene(QObject *parent) :QGraphicsScene(parent), m_fieldHeight(600), m_fieldWidth(320), m_fish(0), m_sea(0) {//初始化背景圖像initBackgournd(); }void MyScene::initBackgournd() {setSceneRect(0, 0, 320, 600);//添加背景的大海m_sea = new BackgroundItem(QPixmap(":/image/background.png"));addItem(m_sea);m_fish = new Fish();//計算游動的X的范圍int minX = m_fish->boundingRect().width() * 0.5;int maxX = m_fieldWidth - m_fish->boundingRect().width() * 0.5;//計算游動的Y的范圍int minY = m_fish->boundingRect().height()*0.5;int maxY = m_fieldHeight - m_fish->boundingRect().height()*0.5;//設置默認位置int currentX = (minX + maxX)*0.5;int currentY = (minY + maxY)*0.5;addItem(m_fish);m_fish->setPos(currentX, currentY);m_fish->setZValue(1); }顯示效果如下所示:
使用定時器
為了能讓圖元動起來,我們添加定時器定時對圖元的位置進行調整,默認定時器是關閉的。當按下對應的方向鍵之后啟動定時器,當松開對應的方向鍵之后暫停定時器。對應的實現如下:
//myscene.h #ifndef MYSCENE_H #define MYSCENE_H#include <QGraphicsScene> #include <QTimer>class QGraphicsPixmapItem; class BackgroundItem; class Fish; class MyScene : public QGraphicsScene {Q_OBJECT public:explicit MyScene(QObject *parent = 0);public slots://對魚進行移動void moveFish(); protected://按鍵按下和抬起事件void keyPressEvent(QKeyEvent *event);void keyReleaseEvent(QKeyEvent *event);private://修改水平位置void addHorizontalInput(int input);//修改垂直位置void addVerticalInput(int input);private://初始化背景圖片void initBackgournd();//檢查定時器void checkTimer();//場景的范圍int m_fieldHeight;int m_fieldWidth;//海洋中的魚Fish* m_fish;BackgroundItem *m_sea; //背景//移動速度int m_velocity;//X方向上的范圍qreal m_minX;qreal m_maxX;//Y方向上的范圍qreal m_minY;qreal m_maxY;//定時器QTimer m_timer;//當前位置qreal m_currentX;qreal m_currentY;//X方向和Y方向上的輸入int m_horizontalInput;int m_verticalInput; };#endif // MYSCENE_H //myscene.cpp #include "myscene.h"#include <QKeyEvent> #include <QPropertyAnimation> #include <QGraphicsView> #include <QPen>#include "fish.h" #include "backgrounditem.h" #include <QGamepadManager> #include <QGamepad> #include <math.h>MyScene::MyScene(QObject *parent) :QGraphicsScene(parent), m_velocity(4), m_minX(0), m_maxX(0), m_minY(0), m_maxY(0), m_fieldHeight(600), m_fieldWidth(320), m_fish(0), m_sea(0), m_horizontalInput(0), m_verticalInput(0) {//初始化背景圖像initBackgournd();//定時器對圖元進行移動m_timer.setInterval(30);connect(&m_timer, &QTimer::timeout, this, &MyScene::moveFish); }void MyScene::moveFish() {//如果兩個方向上都沒有變化不操作對象if ((m_fish->xDirection() == 0)&& (m_fish->yDirection() == 0)){return;}//根據速度向對應的方向上進行移動int xDirection = m_fish->xDirection();int yDirection = m_fish->yDirection();//qBound確保位置在視圖范圍內//X方向變化const int dx = xDirection * m_velocity;qreal newX = qBound(m_minX, m_currentX + dx, m_maxX);m_currentX = newX;//Y方向變化const int dy = yDirection * m_velocity;qreal newY = qBound(m_minY, m_currentY + dy, m_maxY);m_currentY = newY;m_fish->setPos(m_currentX,m_currentY); }void MyScene::checkTimer() {//對定時器進行檢查if ((m_fish->xDirection() == 0)&& (m_fish->yDirection() == 0)){m_timer.stop();}else if (!m_timer.isActive()){m_timer.start();} }void MyScene::initBackgournd() {setSceneRect(0, 0, 320, 600);//添加背景的大海m_sea = new BackgroundItem(QPixmap(":/image/background.png"));addItem(m_sea);m_fish = new Fish();//計算游動的X的范圍m_minX = m_fish->boundingRect().width() * 0.5;m_maxX = m_fieldWidth - m_fish->boundingRect().width() * 0.5;//計算游動的Y的范圍m_minY = m_fish->boundingRect().height()*0.5;m_maxY = m_fieldHeight - m_fish->boundingRect().height()*0.5;//設置默認位置m_currentX = (m_maxX + m_minX)*0.5;m_currentY = (m_maxY + m_minY)*0.5;addItem(m_fish);m_fish->setPos(m_currentX, m_currentY);m_fish->setZValue(1); }void MyScene::keyPressEvent(QKeyEvent *event) {if (event->isAutoRepeat()) {return;}switch (event->key()) {case Qt::Key_Right:addHorizontalInput(1);break;case Qt::Key_Left:addHorizontalInput(-1);break;case Qt::Key_Up:addVerticalInput(-1);break;case Qt::Key_Down:addVerticalInput(1);break;default:break;} }void MyScene::keyReleaseEvent(QKeyEvent *event) {if (event->isAutoRepeat()) {return;}switch (event->key()) {case Qt::Key_Right:addHorizontalInput(-1);break;case Qt::Key_Left:addHorizontalInput(1);break;case Qt::Key_Up:addVerticalInput(1);break;case Qt::Key_Down:addVerticalInput(-1);break;default:break;} } //修改水平移動方向 void MyScene::addHorizontalInput(int input) {m_horizontalInput += input;m_fish->setXDirection(qBound(-1, m_horizontalInput, 1));checkTimer(); } //修改垂直移動方向 void MyScene::addVerticalInput(int input) {m_verticalInput += input;m_fish->setYDirection(qBound(-1, m_verticalInput, 1));checkTimer(); }添加了定時器之后,我們就可以通過方向鍵來控制圖元的運動了,對應的顯示效果如下:
使用動畫框架
使用定時器動態修改圖元的屬性,這種動態操作比較單一。如果想實現更加復雜的動畫效果,可以采用QT的動畫框架。QT的動畫類是基于QObject的,不僅可以用于QWidget控件還可以用于圖形視圖框架中的圖元。使用動畫類,我們不僅可以操作圖元的位置還可以操作圖元的顏色、透明度等一系列的屬性。同時我們還可以將多個動畫組合到一起使用。
動畫類的使用流程如下:
1.創建一個動畫類(比如 QPropertyAnimation)
2.設置動畫對象(setTargetObject)
3.設置需要動態操作的屬性(setPropertyName)
4.設置動畫的變化規則(起始值、結束值及差值曲線)
5.啟動動畫
默認的Scene成員變量動畫類是識別不了的,我們必須把對應的成員值聲明成QT能識別的屬性(Property)。只有繼承自QObject的類才能聲明屬性。我們一般在Q_OBJECT宏之后,在private作用域下通過Q_PROPERTY宏來聲明屬性。在之前的QML和C++交互的文章中也介紹過如何聲明屬性,格式如下:
Q_PROPERTY(變量類型 訪問名稱 READ 讀方法 WRITE 寫方法 NOTIFY 發生變化的通知信號)動畫框架支持的屬性類型包括int、unsinged int、double、float、QLine、QLineF、QSize、QSizeF、QRect、QRectF、QColor。其它的類型不支持,因為QT無法支持其它類型的差值操作。當然我們也可以為自定義類型添加支持。
這里我們在MyScene中添加一個屬性rushDistance用來定義Fish快速移動的距離,對應的實現如下所示:
#ifndef MYSCENE_H #define MYSCENE_H#include <QGraphicsScene> #include <QTimer>class QGraphicsPixmapItem; class QPropertyAnimation; class BackgroundItem; class Fish; class MyScene : public QGraphicsScene {Q_OBJECT//添加一個屬性值Q_PROPERTY(qreal rushDistanceREAD rushDistance //讀方法WRITE setRushDistance //寫方法NOTIFY rushDistanceChanged) //信號 public:explicit MyScene(QObject *parent = 0);//屬性值的讀方法qreal rushDistance() const;//屬性值的寫方法void setRushDistance(const qreal &rushDistance); private slots://魚快速游動void rush(); signals://屬性值變化的信號void rushDistanceChanged(qreal);······//對應的動畫類QPropertyAnimation *m_rush_Animation;//對應的屬性值qreal mRushDistance; };#endif // MYSCENE_H定義完成屬性之后,我們添加一個動畫,動態的操作這個屬性,從而實現Fish的快速移動,對應的實現如下:
MyScene::MyScene(QObject *parent) :QGraphicsScene(parent), m_velocity(4), m_minX(0), m_maxX(0), m_minY(0), m_maxY(0), m_fieldHeight(600), m_fieldWidth(320), m_fish(0), m_sea(0), m_horizontalInput(0), m_verticalInput(0), m_rush_Animation(new QPropertyAnimation(this)), m_last_input(-1) {//初始化背景圖像initBackgournd();······//定義動畫按照固定的速度曲線對屬性值進行調整m_rush_Animation->setTargetObject(this);m_rush_Animation->setPropertyName("rushDistance");m_rush_Animation->setStartValue(0);m_rush_Animation->setKeyValueAt(0.5, 0.5);m_rush_Animation->setEndValue(1.5);m_rush_Animation->setDuration(400);m_rush_Animation->setEasingCurve(QEasingCurve::InCubic); } //啟動動畫,讓魚快速移動 void MyScene::rush() {if (QAbstractAnimation::Stopped == m_rush_Animation->state()) {m_rush_Animation->start();} } //獲取快速移動的值 qreal MyScene::rushDistance() const {return mRushDistance; }void MyScene::setRushDistance(const qreal &rushDistance) {if (mRushDistance == rushDistance) {return;}//動畫執行的過程中動態刷新魚的位置mRushDistance = rushDistance;emit rushDistanceChanged(mRushDistance);int prev_pos = m_currentX;prev_pos += m_last_input * 80 *m_rush_Animation->currentValue().toReal();m_fish->setX(prev_pos);//動畫結束的時候更新魚的當前位置if (1.5 == m_rush_Animation->currentValue()) {m_currentX = prev_pos;} }//按空格鍵啟動動畫 void MyScene::keyPressEvent(QKeyEvent *event) {if (event->isAutoRepeat()) {return;}switch (event->key()) {case Qt::Key_Space:rush();default:break;} }動畫圖元的動畫效果如下所示:
為了讓背景顯示不那么單調,我們添加幾個動態閃爍的星星用來點綴一下,對應的星星圖元的實現如下。這里需要注意一點,默認的圖元并不是繼承自QObject類的,無法注冊屬性,我們實現自定義圖元的時候需要同時繼承實現QObject類和QGraphicsPixmapItem類。當然我們也可以直接繼承QGraphicsObject類,但是我們就需要自己在paint()函數中實現圖片的繪制操作了。
在背景創建的時候,動態的將小星星添加進去,添加代碼如下:
void MyScene::initBackgournd() {······for (int i = 0; i < 25; ++i) {Star *c = new Star();c->setPos(qrand() % (int)m_maxX, qrand() % (int)m_maxY);c->setZValue(0);addItem(c);} }添加了星星背景之后,視圖場景顯示效果如下所示:
通過引入動畫框架我們就可以實現各種各樣的復雜的動畫效果了。這在一些2D游戲開發過程中用的還挺多的。
調用advance()方法
除了定時器和動畫框架外,我們還可以通過調用scene的advance()方法來讓圖元動起來。如果調用Scene的advance()方法,該方法會進一步調用所有圖元的advance方法。我們可以在對應圖元的advance()方法中實現圖元的運動。
Scene的advance()方法調用分為兩步調用,首先使用數值0調用一次,通知圖元準備移動,然后使用數值1調用一次,通知圖元移動。advance()方法通常和QTimeLine搭配到一起使用,對應的調用流程如下所示:
MyScene::MyScene(QObject *parent) :QGraphicsScene(parent) {······//2秒一次,調用十次QTimeLine* timeLine = new QTimeLine(2000,this);timeLine->setFrameRange(0,10);connect(timeLine,SIGNAL(frameChanged(int)),this,SLOT(advance())); }由于所有的圖元都會調用advance方法,所以所有圖元都會移動,如果我們只想移動一部分圖元的話,使用advance()方法可能并不是最好的動畫方案。
總結
以上是生活随笔為你收集整理的QGraphicsView图形视图框架使用(六)图元动画的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【计算机视觉】OpenCV实现单目相机标
- 下一篇: [转载]Palm 串行通讯GPS数据读取