QT的GVF(Graphics View framework)框架提供QGraphicsScene类用于和大量从QGraphicsItem类派生的定制2D图元的管理和交互,和一个支持缩放和旋转的QGraphicsView窗口使这些图元可视化。
Mouse Class 定义
mouse类继承自QGraphicsItem,用于绘制老鼠。QGraphicsItem是所有在GVF中的图元的基类。
绘制定制图元时,必须实现QGraphicsItem的两个纯虚函数:boundingRect()函数返回图元绘制区域的估计值,paint()函数实现绘制工作。
mouse.h
1 #ifndef MOUSE_H 2 #define MOUSE_H 3 4 #include <QGraphicsItem> 5 #include <QTime> 6 #include <QTimer> 7 class Mouse : public QGraphicsItem 8 { 9 public: 10 Mouse(); 11 12 QRectF boundingRect() const; 13 QPainterPath shape() const; 14 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget); 15 16 protected: 17 void advance(int step); 18 19 private: 20 qreal angle; 21 qreal speed; 22 qreal mouseEyeDirection; 23 QColor color; 24 }; 25 26 #endif
mouse.cpp
1 #include "mouse.h" 2 3 #include <QGraphicsScene> 4 #include <QPainter> 5 #include <QStyleOption> 6 7 #include <math.h> 8 9 static const double Pi = 3.14159265358979323846264338327950288419717; 10 static double TwoPi = 2.0 * Pi; 11 12 static qreal normalizeAngle(qreal angle) 13 { 14 while (angle < 0) 15 angle += TwoPi; 16 while (angle > TwoPi) 17 angle -= TwoPi; 18 return angle; 19 } 20 21 Mouse::Mouse() : angle(0), speed(0), mouseEyeDirection(0), color(qrand() % 256, qrand() % 256, qrand() % 256) 22 { 23 setRotation(qrand() % (360 * 16));//顺时针随机旋转图元角度 24 } 25 //boundingRect()函数返回一个矩形范围作为图元的外边界,图元所有的绘制必须在这个矩形范围内完成。 26 QRectF Mouse::boundingRect() const 27 { 28 qreal adjust = 0.5; 29 return QRectF(-18 - adjust, -22 - adjust, 36 + adjust, 60 + adjust); 30 } 31 //QGraphicsView调用paint()函数来绘制图元,该函数采用本地坐标系统(图元坐标系统) 32 //我们使用QGraphicsScene::collidingItems() 函数来检测是否发生碰撞。当发生碰撞时,老鼠的耳朵将被填充为红色,否则为深黄色。 33 //QGraphicsItem::shape()函数采用本地坐标系统可以返回一个精确的图形用于碰撞检测,命中测试和QGraphicsScene :: items()函数。 34 QPainterPath Mouse::shape() const 35 { 36 QPainterPath path; 37 path.addRect(-10, -20, 20, 40);//当碰撞发生在小鼠的身体时即认为发生碰撞 38 return path; 39 } 40 41 void Mouse::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) 42 { 43 // Body 44 painter->setBrush(color); 45 painter->drawEllipse(-10, -20, 20, 40); 46 47 // Eyes 48 painter->setBrush(Qt::white); 49 painter->drawEllipse(-10, -17, 8, 8); 50 painter->drawEllipse(2, -17, 8, 8); 51 52 // Nose 53 painter->setBrush(Qt::black); 54 painter->drawEllipse(QRectF(-2, -22, 4, 4)); 55 56 // Pupils 57 painter->drawEllipse(QRectF(-8.0 + mouseEyeDirection, -17, 4, 4)); 58 painter->drawEllipse(QRectF(4.0 + mouseEyeDirection, -17, 4, 4)); 59 60 // Ears 61 //collidingItems()碰撞检测函数 62 painter->setBrush(scene()->collidingItems(this).isEmpty() ? Qt::darkYellow : Qt::red); 63 painter->drawEllipse(-17, -12, 16, 16); 64 painter->drawEllipse(1, -12, 16, 16); 65 66 // Tail 67 QPainterPath path(QPointF(0, 20)); 68 path.cubicTo(-5, 22, -5, 22, 0, 25); 69 path.cubicTo(5, 27, 5, 32, 0, 30); 70 path.cubicTo(-5, 32, -5, 42, 0, 35); 71 painter->setBrush(Qt::NoBrush); 72 painter->drawPath(path); 73 } 74 //所以当step为0时,我们不做任何操作(advance()函数会被QGraphicsScene :: advance()调用两次,第一次step==0表示图元将要开始移动,当step == 1表示开始移动)。 75 //注意,QGraphicsItem提供的mapFromScene()函数可以把场景坐标映射成系统坐标 76 void Mouse::advance(int step) 77 { 78 if (!step) 79 return; 80 // Don't move too far away 81 QLineF lineToCenter(QPointF(0, 0), mapFromScene(0, 0));//图元坐标原点和场景坐标原点之间的直线 82 if (lineToCenter.length() > 150) //当距离大于150像素时,调整方向 83 { 84 qreal angleToCenter = ::acos(lineToCenter.dx() / lineToCenter.length()); 85 if (lineToCenter.dy() < 0) 86 angleToCenter = TwoPi - angleToCenter; 87 angleToCenter = normalizeAngle((Pi - angleToCenter) + Pi / 2); 88 89 if (angleToCenter < Pi && angleToCenter > Pi / 4) 90 { 91 // Rotate left 92 angle += (angle < -Pi / 2) ? 0.25 : -0.25; 93 } 94 else if (angleToCenter >= Pi && angleToCenter < (Pi + Pi / 2 + Pi / 4)) 95 { 96 // Rotate right 97 angle += (angle < Pi / 2) ? 0.25 : -0.25; 98 } 99 } 100 else if (::sin(angle) < 0) 101 { 102 angle += 0.25; 103 } 104 else if (::sin(angle) > 0) 105 { 106 angle -= 0.25; 107 } 108 109 // Try not to crash with any other mice 110 QList<QGraphicsItem *> dangerMice = scene()->items (QPolygonF() << mapToScene(0, 0) << mapToScene(-30, -50) << mapToScene(30, -50)); 111 foreach(QGraphicsItem *item, dangerMice)//检测场景中的图元 112 { 113 if (item == this)//如果只有自己则跳过,否则调整方向 114 continue; 115 116 QLineF lineToMouse(QPointF(0, 0), mapFromItem(item, 0, 0)); 117 qreal angleToMouse = ::acos(lineToMouse.dx() / lineToMouse.length()); 118 if (lineToMouse.dy() < 0) 119 angleToMouse = TwoPi - angleToMouse; 120 angleToMouse = normalizeAngle((Pi - angleToMouse) + Pi / 2); 121 122 if (angleToMouse >= 0 && angleToMouse < Pi / 2) 123 { 124 // Rotate right 125 angle += 0.5; 126 } 127 else if (angleToMouse <= TwoPi && angleToMouse >(TwoPi - Pi / 2)) 128 { 129 // Rotate left 130 angle -= 0.5; 131 } 132 } 133 134 // Add some random movement 135 if (dangerMice.size() > 1 && (qrand() % 10) == 0) 136 { 137 if (qrand() % 1) 138 angle += (qrand() % 100) / 500.0; 139 else 140 angle -= (qrand() % 100) / 500.0; 141 } 142 143 speed += (-50 + qrand() % 100) / 100.0; 144 145 qreal dx = ::sin(angle) * 10; 146 mouseEyeDirection = (qAbs(dx / 5) < 1) ? 0 : dx / 5; 147 148 setRotation(rotation() + dx); 149 setPos(mapToParent(0, -(3 + sin(speed) * 3))); 150 }
mainwindow.h
1 #pragma once 2 3 #include <QtWidgets/QMainWindow> 4 #include "ui_MainWindow.h" 5 #include <QGraphicsScene> 6 #include <QGraphicsView> 7 #include <QHBoxLayout> 8 #include "mouse.h" 9 class MainWindow : public QMainWindow 10 { 11 Q_OBJECT 12 13 public: 14 MainWindow(QWidget *parent = Q_NULLPTR); 15 public: 16 void ConstructScene(); 17 private: 18 QTimer timer; 19 static const int MouseCount = 8; 20 QGraphicsScene * scene; 21 QGraphicsView *view; 22 Ui::MainWindowClass ui; 23 };
mainwindow.cpp
1 #include "MainWindow.h" 2 3 MainWindow::MainWindow(QWidget *parent) 4 : QMainWindow(parent) 5 { 6 ui.setupUi(this); 7 scene = new QGraphicsScene(this);//创建场景 8 view = new QGraphicsView(scene);//创建视图 9 ui.verticalLayout->addWidget(view);//将场景加入主界面 10 ConstructScene();//构建动画 11 } 12 13 14 void MainWindow::ConstructScene() 15 { 16 qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));//随机数种子 17 scene->setSceneRect(-300, -300, 600, 600); 18 scene->setItemIndexMethod(QGraphicsScene::NoIndex); 19 for (int i = 0; i < MouseCount; ++i) 20 { 21 Mouse *mouse = new Mouse; 22 mouse->setPos(::sin((i * 6.28) / MouseCount) * 200,::cos((i * 6.28) / MouseCount) * 200); 23 scene->addItem(mouse); 24 } 25 26 view->setRenderHint(QPainter::Antialiasing); 27 //view.setBackgroundBrush(QPixmap(":/images/cheese.jpg")); 28 view->setCacheMode(QGraphicsView::CacheBackground); 29 view->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); 30 view->setDragMode(QGraphicsView::ScrollHandDrag); 31 //view->resize(400, 300); 32 //view->show(); 33 34 35 QObject::connect(&timer, SIGNAL(timeout()), scene, SLOT(advance())); 36 timer.start(1000 / 33);//每秒30帧 37 }
运行效果
参考:QT5参考文档