文章目录

  • 创建基本元素
  • 使用定时器
  • 使用动画框架
  • 调用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()函数中实现图片的绘制操作了。

//stars.h
#ifndef STAR_H
#define STAR_H#include <QObject>
#include <QGraphicsPixmapItem>
#include <QPropertyAnimation>class Star : public QObject, public QGraphicsPixmapItem
{Q_OBJECT//声明透明度属性Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity)//声明尺寸变化属性Q_PROPERTY(qreal sacleFactor READ scaleFactor WRITE setScaleFactor)public:explicit Star(QGraphicsItem *parent = 0);enum { Type = UserType + 1 };int type() const;//闪烁void explode();//属性修改qreal scaleFactor() const;void setScaleFactor(const qreal &scaleFactor);private:qreal mScaleFactor;   //尺寸缩放系数QPropertyAnimation *mscaleAnimation; //尺寸缩放动画
};#endif
//stars.cpp
#include "stars.h"#include <QParallelAnimationGroup>
#include <QPen>
#include <QPropertyAnimation>
#include <QSequentialAnimationGroup>Star::Star(QGraphicsItem *parent) :QGraphicsPixmapItem(parent),mScaleFactor(1)
{QPixmap pixmap(":/image/currency.png");setPixmap(pixmap);setOffset(-pixmap.width() / 2, -pixmap.height() / 2);explode();
}int Star::type() const
{return Type;
}void Star::explode()
{//并行动画,两个动画同时执行QParallelAnimationGroup *group = new QParallelAnimationGroup(this);//尺寸变化动画mscaleAnimation = new QPropertyAnimation(this, "sacleFactor");mscaleAnimation->setDuration(2700);mscaleAnimation->setStartValue(0.8);mscaleAnimation->setEndValue(1.5);mscaleAnimation->setEasingCurve(QEasingCurve::OutQuad);group->addAnimation(mscaleAnimation);//透明度变化的动画QPropertyAnimation *fadeAnimation = new QPropertyAnimation(this, "opacity");fadeAnimation->setDuration(2700);fadeAnimation->setStartValue(1);fadeAnimation->setEndValue(0.2);fadeAnimation->setEasingCurve(QEasingCurve::OutQuad);group->addAnimation(fadeAnimation);//动画无限的循环播放group->setLoopCount(-1);group->start();
}qreal Star::scaleFactor() const
{return mScaleFactor;
}void Star::setScaleFactor(const qreal &scaleFactor)
{mScaleFactor = scaleFactor;double scale_factor = mscaleAnimation->currentValue().toReal();QTransform transform;transform.scale(scale_factor,scale_factor);this->setTransform(transform);}

在背景创建的时候,动态的将小星星添加进去,添加代码如下:

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图形视图框架使用(六)图元动画相关推荐

  1. QGraphicsView图形视图框架使用(三)位移变换和图元定位

    文章目录 位移变换 图片渲染 图元定位 场景聚焦 当视图框架中的图元比较多且位置比较散乱的时候,为了操作某个特定的图元,我们需要对图元进行位移变换和定位,而从更加方便的操作管理图元.这里就介绍一下图元 ...

  2. Qt开发技术:图形视图框架(二)场景QGraphicsScene、QGraphicsItem与QGraphicsView详解

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/117660217 长期持续带来更多项目与技术分享,咨询请 ...

  3. Qt图形视图框架图片图元QGraphicsPixmapItem

    文章可能被更新,最新地址:http://www.fearlazy.com/index.php/post/107.html 看这个标题还真有点拗口,一句话里出现了四个图字.这句话的关键词是图片,前面讲了 ...

  4. [Qt教程] 第19篇 2D绘图(九)图形视图框架(上)

    [Qt教程] 第19篇 2D绘图(九)图形视图框架(上) 楼主  发表于 2013-5-4 15:26:20 | 查看: 1798| 回复: 26 图形视图框架(上) 版权声明 导语 在前面讲的基本绘 ...

  5. [Qt教程] 第20篇 2D绘图(十)图形视图框架(下)

    [Qt教程] 第20篇 2D绘图(十)图形视图框架(下) 楼主  发表于 2013-5-4 15:43:02 | 查看: 861| 回复: 0 图形视图框架(下) 版权声明 该文章原创于Qter开源社 ...

  6. 201112阶段二qt图形视图框架

    目录 一.学习的知识点 一)事件 二)图形视图框架 二.上课没有听懂或者没有理解的地方 三.当天学习的收获 一.学习的知识点 一)事件 鼠标事件的创建 组合键 二)图形视图框架 场景类:坐标系以中心为 ...

  7. 基于Qt5图形视图框架的“俄罗斯方块”

    Qt5 图形视图框架QGraphicsView 1.图形视图框架包含三大类:场景类(QGraphicsScene),视图类(QGraphicsView),图元类(QGraphicsItem): 2.对 ...

  8. Qt图形视图框架详解-安晓辉-专题视频课程

    Qt图形视图框架详解-12227人已学习 课程介绍         介绍Qt中的Graphics View Framework,涉及View.Scene.Item的关系,如何自定义QGraphicsI ...

  9. Qt 图形视图框架(Qt MVC)

    什么是MVC MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面显示分离 ...

最新文章

  1. 我为什么弃用GAN?
  2. Nat. Mach. Intell.|从局部解释到全局理解的树模型
  3. What is the purpose for IT Pro in Windows 10 Creators Update
  4. Codeforces Round #303 (Div. 2) E. Paths and Trees 最短路+贪心
  5. 多层AOP 解决AOP执行顺序
  6. 如何让 Mybatis 自动生成代码
  7. 远程管理卡介绍使用场景
  8. java输入一个数字输出_java 输入一个数字,反转输出这个数字的值(实现方法)
  9. linux echo命令
  10. ASP.NET 2.0 中的新增服务、控件与功能概述(四)
  11. linux here用法,linux shell的here document用法(cat EOF)
  12. 经典面试题(42):以下代码将输出的结果是什么?
  13. hdu 1512 Monkey King 左偏树
  14. IIFP之牛刀小试(初识篇)
  15. spm——use maven to test Hello TianZhuang!
  16. 设计模式——代理模式(思维导图)
  17. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(四) 添加表情、群聊功能...
  18. php unpack 原理,unpack
  19. H5特效demo网站
  20. 如何快速制作一个H5单页面网站

热门文章

  1. 小米路由器AX3600开启ssh
  2. linux中ifconfig无法显示ip地址
  3. Python 位运算
  4. 【§炫彩苹果win7主题§】
  5. C++ 请以pass-by-reference-to-const替换pass-by-value
  6. 实验: GVRP 配置,三层交换机通讯 ,VLAN 间路由,单臂路由与路由器子接口的配置,vlan-单臂路由技术
  7. HMI(人机界面)工业自动化中最前沿的解决方案
  8. Spring Framework系统架构
  9. 扫描MAC地址脚本--保存
  10. 天猫登录 post java_天猫登录POST问题