前言:
本文需要了解基本的 Qt 圖形視圖框架結構和座標系統
否則 你可能看不懂
還是看一些 官方demo 可以學到人家一些編碼的東西 今天看到個好玩的 見下圖
發現沒 當小老鼠 碰到以後 耳朵就變紅 也就是有碰撞檢測機制
整個的結構就是
管理場景 管理這些小老鼠 是: QGraphicsScene 類
能拖拉的這個窗口 視口 背景設置爲了 奶酪的 圖片 是 : QGraphicsView 類
移動的小老鼠是: 繼承的 QGraphicsItem 類 實現自己的 類
官方的demo:
還是先看下 項目的 文件結構 :
簡單的
一個 mouse 類
一個 main 函數
一個 jpg 圖片 就是奶酪的背景圖
main 函數
#include <QtWidgets>
#include <math.h>
#include "mouse.h"
static const int MouseCount = 7;
//! [0]
int main(int argc, char **argv)
{
QApplication app(argc, argv);
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
//! [0]
//! [1]
QGraphicsScene scene;
scene.setSceneRect(-300, -300, 600, 600);
//! [1] //! [2]
scene.setItemIndexMethod(QGraphicsScene::NoIndex);
//! [2]
//! [3]
for (int i = 0; i < MouseCount; ++i) {
Mouse *mouse = new Mouse;
mouse->setPos(::sin((i * 6.28) / MouseCount) * 200,
::cos((i * 6.28) / MouseCount) * 200);
scene.addItem(mouse);
}
//! [3]
//! [4]
QGraphicsView view(&scene);
view.setRenderHint(QPainter::Antialiasing);
view.setBackgroundBrush(QPixmap(":/images/cheese.jpg"));
//! [4] //! [5]
view.setCacheMode(QGraphicsView::CacheBackground);
view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
view.setDragMode(QGraphicsView::ScrollHandDrag);
//! [5] //! [6]
view.setWindowTitle(QT_TRANSLATE_NOOP(QGraphicsView, "Colliding Mice"));
view.resize(400, 300);
view.show();
QTimer timer;
QObject::connect(&timer, SIGNAL(timeout()), &scene, SLOT(advance()));
timer.start(1000 / 33);
return app.exec();
}
main 函數 首先 弄了個矩形的場景 (-300,-300,600,600)
也就是 寬度和高度 爲900的 正方形 ,可以去看下座標系統
左上角是負數無窮小 右下角是正數無窮大 中心 是 (0,0)
scene.setItemIndexMethod(QGraphicsScene::NoIndex);
此屬性保存項索引方法。
QGraphicsScene將索引算法應用於場景,以加速項發現函數,如items()和itemAt()。索引是最有效的靜態場景(即。,這裏的物品不會移動)。對於動態場景或具有許多動畫項的場景,索引簿記可能會超過快速查找速度。
對於一般情況,默認索引方法BspTreeIndex工作正常。如果你的場景使用了很多動畫,並且你正在經歷緩慢,你可以通過調用setItemIndexMethod(NoIndex)來禁用索引。
繼續向下 實例化了 7個 mouse 類 並添加到 additem 場景中 這個類等會再看
在向下給場景設置了個視口
給視口設置了背景圖 也就是奶酪的照片
下面設置了視口的 渲染區域模式 拖動模式 標題窗口大小 等等
然後是一個定時器 刷新場景
譯文:這個槽通過調用QGraphicsItem::advance()來將場景向前推進一步。這分兩個階段完成:在第一階段,所有的項目都被通知場景將要改變,在第二階段,所有的項目都被通知它們可以移動。在第一階段,調用QGraphicsItem::advance(),傳遞一個值0作爲參數,在第二階段傳遞1。
很清晰了
下面我們主要看一下 小老鼠 怎麼來 怎麼動的
mouse 類
#ifndef MOUSE_H
#define MOUSE_H
#include <QGraphicsItem>
//! [0]
class Mouse : public QGraphicsItem
{
public:
Mouse();
QRectF boundingRect() const override;
QPainterPath shape() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
protected:
void advance(int step) override;
private:
qreal angle;
qreal speed;
qreal mouseEyeDirection;
QColor color;
};
//! [0]
#endif
Mouse 繼承了 QGraphicsItem 下面的兩個函數 必須要實現
否則編譯不過
原因是 基類 QgraphicsItem 把這個函數 設置爲了純虛函數
基類裏面
#include "mouse.h"
#include <QGraphicsScene>
#include <QPainter>
#include <QStyleOption>
#include <QDebug>
#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;
}
//! [0]
Mouse::Mouse()
: angle(0), speed(0), mouseEyeDirection(0),
color(qrand() % 256, qrand() % 256, qrand() % 256)
{
setRotation(qrand() % (360 * 16));
}
//! [0]
//! [1]
QRectF Mouse::boundingRect() const
{
qreal adjust = 0.5;
return QRectF(-18 - adjust, -22 - adjust,
36 + adjust, 60 + adjust);
}
//! [1]
//! [2]
QPainterPath Mouse::shape() const
{
QPainterPath path;
path.addRect(-10, -20, 20, 40);
return path;
}
//! [2]
//! [3]
void Mouse::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
// Body
painter->setBrush(color);
painter->drawEllipse(-10, -20, 20, 40);
// Eyes
painter->setBrush(Qt::white);
painter->drawEllipse(-10, -17, 8, 8);
painter->drawEllipse(2, -17, 8, 8);
// Nose
painter->setBrush(Qt::black);
painter->drawEllipse(QRectF(-2, -22, 4, 4));
// Pupils
painter->drawEllipse(QRectF(-8.0 + mouseEyeDirection, -17, 4, 4));
painter->drawEllipse(QRectF(4.0 + mouseEyeDirection, -17, 4, 4));
// Ears
painter->setBrush(scene()->collidingItems(this).isEmpty() ? Qt::darkYellow : Qt::red);
painter->drawEllipse(-17, -12, 16, 16);
painter->drawEllipse(1, -12, 16, 16);
// Tail
QPainterPath path(QPointF(0, 20));
path.cubicTo(-5, 22, -5, 22, 0, 25);
path.cubicTo(5, 27, 5, 32, 0, 30);
path.cubicTo(-5, 32, -5, 42, 0, 35);
painter->setBrush(Qt::NoBrush);
painter->drawPath(path);
}
//! [3]
//! [4]
void Mouse::advance(int step)
{
if (!step)
return;
//! [4]
// Don't move too far away
//! [5]
QLineF lineToCenter(QPointF(0, 0), mapFromScene(0, 0));
if (lineToCenter.length() > 150) {
qreal angleToCenter = ::acos(lineToCenter.dx() / lineToCenter.length());
if (lineToCenter.dy() < 0)
angleToCenter = TwoPi - angleToCenter;
angleToCenter = normalizeAngle((Pi - angleToCenter) + Pi / 2);
if (angleToCenter < Pi && angleToCenter > Pi / 4) {
// Rotate left
angle += (angle < -Pi / 2) ? 0.25 : -0.25;
} else if (angleToCenter >= Pi && angleToCenter < (Pi + Pi / 2 + Pi / 4)) {
// Rotate right
angle += (angle < Pi / 2) ? 0.25 : -0.25;
}
} else if (::sin(angle) < 0) {
angle += 0.25;
} else if (::sin(angle) > 0) {
angle -= 0.25;
//! [5] //! [6]
}
//! [6]
// Try not to crash with any other mice
//! [7]
QList<QGraphicsItem *> dangerMice = scene()->items(QPolygonF()
<< mapToScene(0, 0)
<< mapToScene(-30, -50)
<< mapToScene(30, -50));
foreach (QGraphicsItem *item, dangerMice) {
if (item == this)
continue;
QLineF lineToMouse(QPointF(0, 0), mapFromItem(item, 0, 0));
qreal angleToMouse = ::acos(lineToMouse.dx() / lineToMouse.length());
if (lineToMouse.dy() < 0)
angleToMouse = TwoPi - angleToMouse;
angleToMouse = normalizeAngle((Pi - angleToMouse) + Pi / 2);
if (angleToMouse >= 0 && angleToMouse < Pi / 2) {
// Rotate right
angle += 0.5;
} else if (angleToMouse <= TwoPi && angleToMouse > (TwoPi - Pi / 2)) {
// Rotate left
angle -= 0.5;
//! [7] //! [8]
}
//! [8] //! [9]
}
//! [9]
// Add some random movement
//! [10]
if (dangerMice.size() > 1 && (qrand() % 10) == 0) {
if (qrand() % 1)
angle += (qrand() % 100) / 500.0;
else
angle -= (qrand() % 100) / 500.0;
}
//! [10]
//! [11]
speed += (-50 + qrand() % 100) / 100.0;
qreal dx = ::sin(angle) * 10;
mouseEyeDirection = (qAbs(dx / 5) < 1) ? 0 : dx / 5;
setRotation(rotation() + dx);
setPos(mapToParent(0, -(3 + sin(speed) * 3)));
}
//! [11]
這個函數 返回的是 這個自定義的 item 外包圍的矩形框 碰撞檢測 以及其他的都是根據這個矩形框的大小來判斷的
這個幾個變量 在構造函數裏 都給賦了初始值
角度 0 速度0 眼睛的方向0 隨機顏色 隨機的角度
剛纔是 我還以爲 這個老鼠是圖片 沒想到 是paint 畫出來的
這個註釋 很清楚了
身體 眼睛 鼻子 耳朵 尾巴 等等等
畫橢圓 圓形 曲線 圖形疊加構成的小老鼠
咱們看一下 老鼠怎麼跑動的
這個函數就是 在 mian 裏面 用 定時器來調動的
這裏面有一些 數學知識 sin cos 角度 啥的 不用細看 我也看不太懂
上面的註釋說了 不能讓老鼠跑太遠
如果老鼠 和 scene 的(0,0) 也就是 場景 中心的 距離 大於 150 就要 做一些處理了
lineToCenter.length() 內部實現就是用 兩點直線距離公式算的
下面寫的我也沒細看 沒看太懂 就是讓他旋轉個角度 ,不讓他向外跑就行
碰撞檢測
場景裏面 提供了個碰撞檢測的 函數
返回與項衝突的所有項的列表。衝突是通過調用QGraphicsItem::collidesWithItem()來確定的;碰撞檢測由模式決定。默認情況下,形狀與項目相交或包含在項目形狀內的所有項目都將返回。
項目按遞減堆疊順序返回(即,列表中的第一項是最上面的項,最後一項是最下面的項)。
當老鼠碰到其他老鼠 就把耳朵變爲 紅色
找 範圍 -30 50 裏面所有的 item(老鼠)
如果 找到了 其他的老鼠
讓他旋轉個角度
增加一些隨機移動的元素
這裏就是根據上面 算的角度 和 位置 讓小老鼠 動起來
沒了 到這裏就結束了 跑動的小老鼠 用這套框架 輕鬆的就完成了