Qt 視圖框架示例 Colliding Mice 的翻譯
簡介:
該示例程序介紹了怎樣在Qt的視圖框架裏創建動畫以及衝突檢測。
視圖框架提供了QGraphicsScene class 場景類來管理2D圖形對象.一個QGraphicsView widget 來是顯示這些圖形項,並且支持視圖的縮放和旋轉。
在該示例中, Mouse class 自定義了一些Mice 的圖形項,main() 函數提供了應用程序的窗體。
首先我們來學習Mouse 類是如何實現動畫和衝突檢測的。然後我們學習main()函數是如何將mice 圖形項添加到場景中。
Mouse Class 定義
The mouse class繼承自QGraphicsItem. QGraphicsItem是視圖框架中所有圖形項的基類。它提供了一個輕量級的框架來創建用戶自定義的一些圖形項。
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 = 0;
qreal speed = 0;
qreal mouseEyeDirection = 0;
QColor color;
};
當我們創建自定義圖形項的時候,我們必須要重寫兩個虛函數
1、 boundingRect(), 繪圖的邊界
2、 paint(), 繪圖
我們還重寫了兩個函數shape()和advance().
1、shape():mice 圖形項的準確形狀。默認情況下,返回的是bounding rectangle.
2、advance():實現動畫
Mouse Class 定義
構造函數:初始化一些私有變量
Mouse::Mouse() : color(QRandomGenerator::global()->bounded(256),
QRandomGenerator::global()->bounded(256),
QRandomGenerator::global()->bounded(256))
{
setRotation(QRandomGenerator::global()->bounded(360 * 16));
}
這裏我們用QRandomGenerator. 來實現Mice的不同膚色。
setRotation()來改變Mice 的方向。
QGraphicsScene 會在幀的推進中會調用每一圖形項的advance()功能,這裏我們重寫了這個advance()方法,於是Mice會 一幀幀的動起來了。
void Mouse::advance(int step)
{
if (!step)
return;
QLineF lineToCenter(QPointF(0, 0), mapFromScene(0, 0));
if (lineToCenter.length() > 150) {
qreal angleToCenter = std::atan2(lineToCenter.dy(), lineToCenter.dx());
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;
}
因爲advance()被調用兩次,第一次是step=0;然後是step=1;前一次不作任何動作。同時,我們要保證mice待在一個半徑是150像素的圓內。
mapFromScene() 是完成從圖形項座標到場景座標的映射。
const QList<QGraphicsItem *> dangerMice = scene()->items(QPolygonF()
<< mapToScene(0, 0)
<< mapToScene(-30, -50)
<< mapToScene(30, -50));
for (const QGraphicsItem *item : dangerMice) {
if (item == this)
continue;
QLineF lineToMouse(QPointF(0, 0), mapFromItem(item, 0, 0));
qreal angleToMouse = std::atan2(lineToMouse.dy(), lineToMouse.dx());
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;
}
}
if (dangerMice.size() > 1 && QRandomGenerator::global()->bounded(10) == 0) {
if (QRandomGenerator::global()->bounded(1))
angle += QRandomGenerator::global()->bounded(1 / 500.0);
else
angle -= QRandomGenerator::global()->bounded(1 / 500.0);
}
然後我們要實現老鼠的碰撞的處理
speed += (-50 + QRandomGenerator::global()->bounded(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)));
}
最後,我們計算mice的速度,和方向然後設定一個新的座標。
QGraphicsItem::setPos() 函數會設定一個父窗體的座標系統的位置。
應爲圖形項是沒有父對象的,所以需要mapToParent() 將座標映射到需要設置的父窗體的座標系中。如果沒有設定父窗體,默認是映射到場景的座標系中。
QRectF Mouse::boundingRect() const
{
qreal adjust = 0.5;
return QRectF(-18 - adjust, -22 - adjust,
36 + adjust, 60 + adjust);
}
boundingRect() 定義了圖形項的矩形邊界。Qt中的視圖框架是通過該函數的返回值類決定是否需要重畫圖形項。
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);
}
paint()函數執行實際的圖形項的繪製。繪製時的座標系都是圖形項自身的座標系。
mice 的耳朵在碰撞是會顯示紅色。沒有碰撞時是灰黑色。
在Qt的視圖框架裏通過shape-shape 交互來檢測識別碰撞衝突,所以shape()函數裏返回的shape值必須要準確。
QPainterPath Mouse::shape() const
{
QPainterPath path;
path.addRect(-10, -20, 20, 40);
return path;
}
當shapes 變得複雜的時候,shape-shape 重疊檢測也會變得複雜,這會耗費計算的時間。重寫collidesWithItem()該函數可以自定義衝突的算法。
接着我們來看main()函數
The Main() 函數
int main(int argc, char **argv)
{
QApplication app(argc, argv);
首先我們創建一個應用程序,並且創建一個場景。
QGraphicsScene scene;
scene.setSceneRect(-300, -300, 600, 600);
QGraphicsScene提供了放置QGraphicsItems 的一個容器。該容器提供了一些函數方法來更好的顯示,管理圖形項。
當我們創建了一個場景,我們推介設定該場景的大小。若過沒有設定大小,場景的大小會隨着圖形項的增加,而不斷增大。
場景會給每一個圖形項一個索引號,也可以不設定索引號。索引號可以快速的定位到圖形項。
scene.setItemIndexMethod(QGraphicsScene::NoIndex);
QgrapbhicsScene使用下標來高效的管理item的位置,默認的使用BSP樹,適用於一個大型的場景,其中的item都是靜止不變的,可以選擇調用setItemIndexMethod().來禁用下標,可是查看itemIndexMethod來獲取更多的信息
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);
}
//創建mice 圖形項
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);
}
//然後將這些圖形項添加到場景中
QGraphicsView view(&scene);
view.setRenderHint(QPainter::Antialiasing);
view.setBackgroundBrush(QPixmap(":/images/cheese.jpg"));
爲了能夠歐看到場景,我們需要創建一個場景的窗體,QGraphicsView 類 可以提供一個自帶滾動條的窗體。我們確保圖形的渲染是抗鋸齒的。通過背景刷繪製一個背景。
view.setCacheMode(QGraphicsView::CacheBackground);
view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
view.setDragMode(QGraphicsView::ScrollHandDrag);
設置緩存機制。QGraphicsView 可以預渲染像素化的圖形。
設定拖拽模式。ScrollHandDrag 該模式下,當鼠標拖拽時會變爲手型,同時滾動條會隨之變化。
view.setWindowTitle(QT_TRANSLATE_NOOP(QGraphicsView, "Colliding Mice"));
view.resize(400, 300);
view.show();
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, &scene, &QGraphicsScene::advance);
timer.start(1000 / 33);
return app.exec();
}
最後設定窗體的標題,創建一個定時器,設定定時器的超時事件來調用場景的幀的步進。